[java-oidfed-common] branch main updated: Initial (incomplete) import from java-idp-plugin-oidc-op-oidfed
Codeberg
noreply at shibboleth.net
Thu May 7 11:44:54 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/5e8676b33d5dca22237ab36c88fa6e0ddceb9c8e
The following commit(s) were added to refs/heads/main by this push:
new 5e8676b Initial (incomplete) import from java-idp-plugin-oidc-op-oidfed
5e8676b is described below
commit 5e8676b33d5dca22237ab36c88fa6e0ddceb9c8e
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu May 7 14:44:25 2026 +0300
Initial (incomplete) import from java-idp-plugin-oidc-op-oidfed
Following API packages were imported:
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim
- net.shibboleth.idp.plugin.oidc.op.oidfed.profile
- net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config
- net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate
Following impl packages were imported:
- net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache (and all sub-packages)
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.constraints
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.constraints.impl
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.impl
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.jackson
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.impl
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.impl
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl
- net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.util
- net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl
The packages were renamed with the following login: 'net.shibboleth.idp.plugin.oidc.op.oidfed' -> 'net.shibboleth.oidfed'
---
.gitignore | 18 +
oidfed-common-api/pom.xml | 111 ++
.../shibboleth/oidfed/metadata/BaseJWTWrapper.java | 50 +
.../shibboleth/oidfed/metadata/BasePayload.java | 45 +
.../oidfed/metadata/EntityConfiguration.java | 24 +
.../oidfed/metadata/EntityStatement.java | 37 +
.../oidfed/metadata/ResolveEntityResponse.java | 24 +
.../shibboleth/oidfed/metadata/SignedKeyset.java | 25 +
.../oidfed/metadata/SubordinateStatement.java | 24 +
.../net/shibboleth/oidfed/metadata/TrustMark.java | 24 +
.../oidfed/metadata/TrustMarkStatus.java | 24 +
.../payload/BaseExpirableSubjectPayload.java | 39 +
.../metadata/payload/BaseMetadataPayload.java | 30 +
.../payload/EntityConfigurationPayload.java | 67 +
.../metadata/payload/EntityStatementPayload.java | 41 +
.../payload/ResolveEntityResponsePayload.java | 41 +
.../metadata/payload/SignedKeysetPayload.java | 33 +
.../payload/SubordinateStatementPayload.java | 59 +
.../oidfed/metadata/payload/TrustMarkPayload.java | 51 +
.../metadata/payload/TrustMarkStatusPayload.java | 40 +
.../oidfed/metadata/payload/claim/Metadata.java | 83 ++
.../metadata/payload/claim/TrustMarkOwner.java | 49 +
.../oidfed/profile/ProvidedTrustChainResolver.java | 32 +
.../oidfed/profile/TrustedRemoteEntity.java | 74 ++
.../profile/TrustedRemoteResolverEntity.java | 81 ++
...nAutomaticRegistrationProfileConfiguration.java | 54 +
...ionEntityConfigurationProfileConfiguration.java | 82 ++
...tityStatementProducingProfileConfiguration.java | 73 +
...onExplicitRegistrationProfileConfiguration.java | 52 +
.../config/OIDFederationProfileConfiguration.java | 23 +
...FederationRegistrationProfileConfiguration.java | 40 +
...ederationResolveEntityProfileConfiguration.java | 56 +
...erationResponseCachingProfileConfiguration.java | 47 +
...FederationSignedKeysetProfileConfiguration.java | 32 +
...ionTrustMarkValidatingProfileConfiguration.java | 74 ++
.../navigate/AuthorityHintsLookupFunction.java | 48 +
.../CachedErrorResponseLifetimeLookupFunction.java | 46 +
...achedSuccessResponseLifetimeLookupFunction.java | 46 +
.../ClaimsSetDecorationTypeLookupFunction.java | 50 +
...laimsSetManipulationStrategyLookupFunction.java | 52 +
.../EntityStatementLifetimeLookupFunction.java | 52 +
.../LocalMetadataPolicyLookupFunction.java | 49 +
.../MandatoryTrustMarksLookupFunction.java | 48 +
.../MaximumTrustMarkLifetimeLookupFunction.java | 52 +
...tionalClaimsLookupStrategiesLookupFunction.java | 52 +
...ChainClaimsSetDecorationTypeLookupFunction.java | 50 +
.../RemoteTrustMarkValidationPredicate.java | 43 +
.../TokenEndpointAuthMethodLookupFunction.java | 56 +
oidfed-common-dist/pom.xml | 88 ++
.../src/main/assembly/assembly-tgz.xml | 72 +
.../src/main/enforcer/shibbolethKeys.gpg | Bin 0 -> 55669 bytes
.../src/main/resources/bootstrap/id.property | 1 +
.../src/main/resources/bootstrap/keys.txt | 375 ++++++
oidfed-common-impl/pom.xml | 146 ++
.../net/shibboleth/oidfed/OIDFedCommonModule.java | 37 +
.../net/shibboleth/oidfed/OIDFedCommonPlugin.java | 49 +
.../messaging/impl/AbstractSignedJWTResponse.java | 87 ++
.../impl/EntityConfigurationResponse.java | 55 +
.../impl/ExplicitClientRegistrationRequest.java | 102 ++
.../impl/ExplicitClientRegistrationResponse.java | 88 ++
.../messaging/impl/ResolveEntityRequest.java | 140 ++
.../messaging/impl/ResolveEntityResponse.java | 87 ++
.../messaging/impl/SignedKeysetResponse.java | 55 +
...FederationEndpointResponseFetchingStrategy.java | 328 +++++
...actTrustEngineSignatureValidationComponent.java | 103 ++
.../cache/BaseExpirableMetadataContainer.java | 61 +
.../cache/BaseExpirableStatementContainer.java | 97 ++
...icationSecurityConfigurationLookupFunction.java | 117 ++
.../DefaultClientAuthenticationLookupFunction.java | 113 ++
...tEntityStatementContentValidationCondition.java | 181 +++
...faultEntityStatementMetadataFilterStrategy.java | 120 ++
...ntityStatementSignatureValidationCondition.java | 96 ++
...atementValidationCriteriaSetLookupFunction.java | 116 ++
...tPrivateKeyJwtClientAuthenticationFunction.java | 317 +++++
...ultResponseContainerExpirationTimeStrategy.java | 47 +
...DefaultTrustChainHeaderValidationCondition.java | 196 +++
...FederationEndpointEntityStatementCriterion.java | 78 ++
.../metadata/cache/IssuerEntityIDCriterion.java | 78 ++
.../cache/IssuerEntityStatementCriterion.java | 78 ++
.../cache/PreSelectedTrustChainCriterion.java | 80 ++
.../ResponseContainerExpirationCriterion.java | 79 ++
...ignatureValidationKeyContainerJwtCriterion.java | 101 ++
.../metadata/cache/SubjectEntityIDCriterion.java | 79 ++
.../cache/SubjectEntityStatementCriterion.java | 78 ++
.../cache/SubjectLocallyTrustedKeysCriterion.java | 79 ++
.../metadata/cache/SubjectStatementCriterion.java | 79 ++
.../cache/TrustAnchorEntityIDsCriterion.java | 80 ++
.../oidfed/metadata/cache/TrustMarkCriterion.java | 79 ++
.../metadata/cache/TrustMarkOwnersCriterion.java | 81 ++
...tityConfigurationCriteriaSetLookupFunction.java | 47 +
...DefaultEntityConfigurationFetchingStrategy.java | 138 ++
.../EntityConfigurationContainer.java | 59 +
.../ProvidedEntityConfigurationContainer.java | 54 +
...videdEntityConfigurationContainerCriterion.java | 88 ++
...faultSignedKeysetCriteriaSetLookupFunction.java | 49 +
.../DefaultSignedKeysetFetchingStrategy.java | 145 ++
.../cache/keyset/SignedKeysetCacheIdentifier.java | 105 ++
.../cache/keyset/SignedKeysetContainer.java | 68 +
.../cache/keyset/SubjectSignedKeysetCriterion.java | 78 ++
.../keyset/SubjectSignedKeysetUriCriterion.java | 79 ++
...onfigurationMetadataSkeletonLookupStrategy.java | 86 ++
...ntityConfigurationResponseFetchingStrategy.java | 60 +
...StatementSignatureValidationFilterStrategy.java | 63 +
.../local/DefaultLocalTrustAnchorsValidator.java | 58 +
...busResponseContainerExpirationTimeStrategy.java | 58 +
...faultResolveEntityResponseFetchingStrategy.java | 67 +
.../metadata/cache/local/LocalKeyContainer.java | 55 +
.../cache/local/NimbusResponseContainer.java | 71 +
.../cache/local/NimbusResponseCriterion.java | 80 ++
.../cache/local/ResolveEntityRequestCriterion.java | 80 ++
.../local/ResolveEntityResponseContainer.java | 61 +
...ultResolveEntityTrustChainFetchingStrategy.java | 162 +++
.../ResolveEntityCacheContainerIdentifier.java | 143 ++
.../ResolveEntityCacheIdentifierCriterion.java | 78 ++
.../resolver/ResolveEntityResponseContainer.java | 69 +
...inateStatementCriteriaToIdentifierStrategy.java | 93 ++
...efaultSubordinateStatementFetchingStrategy.java | 236 ++++
.../SubordinateStatementCacheIdentifier.java | 103 ++
.../subordinate/SubordinateStatementContainer.java | 69 +
.../DefaultTrustChainFetchingStrategy.java | 463 +++++++
...rustChainSignatureValidationFilterStrategy.java | 176 +++
...stChainSubjectIdentifierExtractionStrategy.java | 45 +
.../trustchain/ProvidedTrustChainContainer.java | 49 +
.../ProvidedTrustChainContainerCriterion.java | 81 ++
.../cache/trustchain/TrustChainsContainer.java | 98 ++
.../DefaultTrustMarkFetchingStrategy.java | 158 +++
.../DefaultTrustMarkStatusFetchingStrategy.java | 144 ++
...ustMarkValidationCriteriaSetLookupFunction.java | 124 ++
.../cache/trustmark/TrustMarkCacheIdentifier.java | 120 ++
.../cache/trustmark/TrustMarkContainer.java | 68 +
.../trustmark/TrustMarkIdentifierCriterion.java | 78 ++
.../trustmark/TrustMarkStatusCacheIdentifier.java | 103 ++
.../cache/trustmark/TrustMarkStatusContainer.java | 80 ++
.../TrustMarkStatusIdentifierCriterion.java | 78 ++
.../constraints/FederationPolicyConstraint.java | 42 +
.../FederationPolicyConstraintHelper.java | 78 ++
.../impl/AbstractFederationPolicyConstraint.java | 82 ++
.../impl/DefaultAllowedEntityTypesConstraint.java | 160 +++
.../impl/DefaultMaxPathLengthConstraint.java | 67 +
.../impl/DefaultNamingConstraintsConstraint.java | 122 ++
.../metadata/impl/AbstractJWTWrapperImpl.java | 92 ++
.../metadata/impl/EntityConfigurationImpl.java | 65 +
.../oidfed/metadata/impl/EntityStatementImpl.java | 59 +
.../metadata/impl/ResolveEntityResponseImpl.java | 64 +
.../oidfed/metadata/impl/SignedKeysetImpl.java | 63 +
.../metadata/impl/SubordinateStatementImpl.java | 64 +
.../oidfed/metadata/impl/TrustMarkImpl.java | 63 +
.../oidfed/metadata/impl/TrustMarkStatusImpl.java | 64 +
.../metadata/jackson/InstantDeserializer.java | 52 +
.../oidfed/metadata/jackson/JWKDeserializer.java | 72 +
.../metadata/jackson/JWKSetDeserializer.java | 72 +
.../jackson/LocalKeyContainerDeserializer.java | 58 +
.../metadata/payload/claim/impl/MetadataImpl.java | 293 ++++
.../payload/claim/impl/TrustMarkOwnerImpl.java | 120 ++
.../impl/BaseExpirableSubjectPayloadImpl.java | 102 ++
.../payload/impl/BaseMetadataPayloadImpl.java | 66 +
.../metadata/payload/impl/BasePayloadImpl.java | 131 ++
.../impl/EntityConfigurationPayloadImpl.java | 186 +++
.../payload/impl/EntityStatementPayloadImpl.java | 108 ++
.../impl/ResolveEntityResponsePayloadImpl.java | 106 ++
.../payload/impl/SignedKeysetPayloadImpl.java | 82 ++
.../impl/SubordinateStatementPayloadImpl.java | 161 +++
.../payload/impl/TrustMarkPayloadImpl.java | 149 +++
.../payload/impl/TrustMarkStatusPayloadImpl.java | 101 ++
.../FederationMetadataPolicyDeserializer.java | 159 +++
.../policy/FederationMetadataPolicyHelper.java | 80 ++
.../policy/FederationMetadataPolicyOperator.java | 57 +
.../AbstractFederationMetadataPolicyOperator.java | 56 +
...DefaultFederationMetadataPolicyAddOperator.java | 115 ++
...ultFederationMetadataPolicyDefaultOperator.java | 44 +
.../DefaultFederationMetadataPolicyEnforcer.java | 95 ++
...tFederationMetadataPolicyEssentialOperator.java | 46 +
...ultFederationMetadataPolicyMergingStrategy.java | 102 ++
...faultFederationMetadataPolicyOneOfOperator.java | 72 +
...ltFederationMetadataPolicySubsetOfOperator.java | 94 ++
...FederationMetadataPolicySupersetOfOperator.java | 92 ++
...faultFederationMetadataPolicyValueOperator.java | 88 ++
.../metadata/util/EntityStatementHelper.java | 203 +++
.../java/net/shibboleth/oidfed/package-info.java | 18 +
...FederationRegistrationProfileConfiguration.java | 196 +++
...erationResponseCachingProfileConfiguration.java | 90 ++
...nAutomaticRegistrationProfileConfiguration.java | 117 ++
...ionEntityConfigurationProfileConfiguration.java | 237 ++++
...onExplicitRegistrationProfileConfiguration.java | 341 +++++
...ederationResolveEntityProfileConfiguration.java | 274 ++++
...FederationSignedKeysetProfileConfiguration.java | 199 +++
.../META-INF/net.shibboleth.idp/postconfig.xml | 1404 ++++++++++++++++++++
.../services/net.shibboleth.idp.module.IdPModule | 1 +
.../services/net.shibboleth.idp.plugin.IdPPlugin | 2 +
.../net/shibboleth/oidfed/module.properties | 9 +
.../net/shibboleth/oidfed/plugin.properties | 6 +
pom.xml | 190 +++
192 files changed, 18424 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b1d018d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+# A simulation of Subversion default ignores, generated by reposurgeon.
+*~
+*.#*
+.*.swp
+.DS_store
+# Simulated Subversion default ignores end here
+# The contents of the svn:ignore property on the branch root.
+*/test-output
+/target
+/oidfed-common-impl/classpath:
+.vscode
+.classpath
+.project
+.settings
+/oidfed-common-api/target
+/oidfed-common-impl/target
+/oidfed-common-dist/target
+
diff --git a/oidfed-common-api/pom.xml b/oidfed-common-api/pom.xml
new file mode 100644
index 0000000..c2e79d6
--- /dev/null
+++ b/oidfed-common-api/pom.xml
@@ -0,0 +1,111 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.shibboleth.oidfed</groupId>
+ <artifactId>oidfed-common-parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>oidfed-common-api</artifactId>
+ <packaging>jar</packaging>
+ <name>Shibboleth IdP :: Plugins :: OpenID Federation Common Library :: API</name>
+ <description>API interfaces and supporting classes for Shibboleth OIDFed Java common library</description>
+
+ <properties>
+ <checkstyle.configLocation>${project.basedir}/../resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
+ <automatic.module.name>net.shibboleth.oidfed.api</automatic.module.name>
+ </properties>
+
+ <dependencies>
+ <!-- Provided dependencies -->
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-crypto-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-profile-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-metadata-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-authn-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${shib-profile.groupId}</groupId>
+ <artifactId>shib-profile-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-messaging-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-security-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-profile-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${shib-shared.groupId}</groupId>
+ <artifactId>shib-support</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>content-type</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.minidev</groupId>
+ <artifactId>json-smart</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>oauth2-oidc-sdk</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-profile-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/BaseJWTWrapper.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/BaseJWTWrapper.java
new file mode 100644
index 0000000..9db6ed1
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/BaseJWTWrapper.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidfed.metadata;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * Base wrapper interface containing {@link SignedJWT} and its parsed payload.
+ *
+ * @param <T> payload format
+ */
+public interface BaseJWTWrapper<T extends BasePayload> {
+
+ /**
+ * Get the entity statement as JWT.
+ *
+ * @return JWT
+ */
+ @Nonnull public SignedJWT getJwt();
+
+ /**
+ * Get the issuer claim of the entity statement.
+ *
+ * @return issuer
+ */
+ @Nonnull @NotEmpty public String getIssuer();
+
+ /**
+ * Get the payload of the entity statement.
+ *
+ * @return payload
+ */
+ @Nonnull public T getParsedPayload();
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/BasePayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/BasePayload.java
new file mode 100644
index 0000000..8e1ac0a
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/BasePayload.java
@@ -0,0 +1,45 @@
+/*
+ * 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.oidfed.metadata;
+
+import java.time.Instant;
+import java.util.Map;
+
+/**
+ * Base payload for claims used within all payloads.
+ */
+public interface BasePayload {
+
+ /**
+ * Get the issuer.
+ *
+ * @return issuer
+ */
+ public String getIssuer();
+
+ /**
+ * Get the issuance time.
+ *
+ * @return issuance time
+ */
+ public Instant getIssuedAt();
+
+ /**
+ * Get the map of custom claims.
+ *
+ * @return The map of any other claims not directly mapped.
+ */
+ public Map<String, Object> getCustomClaims();
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/EntityConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/EntityConfiguration.java
new file mode 100644
index 0000000..9c8c8a7
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/EntityConfiguration.java
@@ -0,0 +1,24 @@
+/*
+ * 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.oidfed.metadata;
+
+import net.shibboleth.oidfed.metadata.payload.EntityConfigurationPayload;
+
+/**
+ * A wrapper interface extending {@link EntityStatement} with parsed claims related to entity configuration.
+ */
+public interface EntityConfiguration extends EntityStatement<EntityConfigurationPayload> {
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/EntityStatement.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/EntityStatement.java
new file mode 100644
index 0000000..cb02f11
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/EntityStatement.java
@@ -0,0 +1,37 @@
+/*
+ * 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.oidfed.metadata;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.payload.EntityStatementPayload;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * A wrapper inteface containing the {@link SignedJWT} and parsed claims related to entity statement.
+ *
+ * @param <T> payload format
+ */
+public interface EntityStatement<T extends EntityStatementPayload> extends BaseJWTWrapper<T> {
+
+ /**
+ * Get the subject claim of the entity statement.
+ *
+ * @return subject
+ */
+ @Nonnull @NotEmpty public String getSubject();
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/ResolveEntityResponse.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/ResolveEntityResponse.java
new file mode 100644
index 0000000..7e125a5
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/ResolveEntityResponse.java
@@ -0,0 +1,24 @@
+/*
+ * 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.oidfed.metadata;
+
+import net.shibboleth.oidfed.metadata.payload.ResolveEntityResponsePayload;
+
+/**
+ * A wrapper interface extending {@link BaseJWTWrapper} with parsed claims related to resolve entity response.
+ */
+public interface ResolveEntityResponse extends BaseJWTWrapper<ResolveEntityResponsePayload> {
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/SignedKeyset.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/SignedKeyset.java
new file mode 100644
index 0000000..58ccdff
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/SignedKeyset.java
@@ -0,0 +1,25 @@
+/*
+ * 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.oidfed.metadata;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.payload.SignedKeysetPayload;
+
+/**
+ * A wrapper inteface containing the {@link SignedJWT} and parsed claims related to signed keyset.
+ */
+public interface SignedKeyset extends BaseJWTWrapper<SignedKeysetPayload> {
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/SubordinateStatement.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/SubordinateStatement.java
new file mode 100644
index 0000000..6a305a2
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/SubordinateStatement.java
@@ -0,0 +1,24 @@
+/*
+ * 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.oidfed.metadata;
+
+import net.shibboleth.oidfed.metadata.payload.SubordinateStatementPayload;
+
+/**
+ * A wrapper interface extending {@link EntityStatement} with parsed claims related to subordinate statements.
+ */
+public interface SubordinateStatement extends EntityStatement<SubordinateStatementPayload> {
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/TrustMark.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/TrustMark.java
new file mode 100644
index 0000000..234d76f
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/TrustMark.java
@@ -0,0 +1,24 @@
+/*
+ * 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.oidfed.metadata;
+
+import net.shibboleth.oidfed.metadata.payload.TrustMarkPayload;
+
+/**
+ * A wrapper interface extending {@link BaseJWTWrapper} with parsed claims related to trust mark.
+ */
+public interface TrustMark extends BaseJWTWrapper<TrustMarkPayload> {
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/TrustMarkStatus.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/TrustMarkStatus.java
new file mode 100644
index 0000000..b5551cc
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/TrustMarkStatus.java
@@ -0,0 +1,24 @@
+/*
+ * 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.oidfed.metadata;
+
+import net.shibboleth.oidfed.metadata.payload.TrustMarkStatusPayload;
+
+/**
+ * A wrapper interface extending {@link BaseJWTWrapper} with parsed claims related to trust mark status.
+ */
+public interface TrustMarkStatus extends BaseJWTWrapper<TrustMarkStatusPayload> {
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/BaseExpirableSubjectPayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/BaseExpirableSubjectPayload.java
new file mode 100644
index 0000000..0cbb3cd
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/BaseExpirableSubjectPayload.java
@@ -0,0 +1,39 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import java.time.Instant;
+
+import net.shibboleth.oidfed.metadata.BasePayload;
+
+/**
+ * Base payload carrying subject and expiration.
+ */
+public interface BaseExpirableSubjectPayload extends BasePayload {
+
+ /**
+ * Get the subject.
+ *
+ * @return subject
+ */
+ public String getSubject();
+
+ /**
+ * Get the expiration time.
+ *
+ * @return expiration time
+ */
+ public Instant getExpiration();
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/BaseMetadataPayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/BaseMetadataPayload.java
new file mode 100644
index 0000000..52ca4bd
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/BaseMetadataPayload.java
@@ -0,0 +1,30 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import net.shibboleth.oidfed.metadata.payload.claim.Metadata;
+
+/**
+ * Base payload carrying metadata claim.
+ */
+public interface BaseMetadataPayload extends BaseExpirableSubjectPayload {
+
+ /**
+ * Get the metadata.
+ *
+ * @return metadata
+ */
+ public Metadata getMetadata();
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/EntityConfigurationPayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/EntityConfigurationPayload.java
new file mode 100644
index 0000000..f949d71
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/EntityConfigurationPayload.java
@@ -0,0 +1,67 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.payload.claim.TrustMarkOwner;
+
+/**
+ * Entity Configuration payload claims as defined by the OpenID Federation 1.0 Section 3.2. This class extends the
+ * list of claims defined by {@link EntityStatementPayload} with the claims that MUST or MAY appear in Entity
+ * Configurations but not in Subordinate Statements.
+ */
+public interface EntityConfigurationPayload extends EntityStatementPayload {
+
+ /**
+ * Get the array of strings representing the Entity Identifiers of Immediate Superiors.
+ *
+ * @return authority hints
+ */
+ @Nullable public List<String> getAuthorityHints();
+
+ /**
+ * Get the array of strings representing the Entity Identifiers of Trust Anchors trusted by the Entity.
+ *
+ * @return trust anchor hints
+ */
+ @Nullable public List<String> getTrustAnchorHints();
+
+ /**
+ * Get the array of Trust Marks.
+ *
+ * @return trust marks
+ */
+ @Nullable public List<Map<String, String>> getTrustMarks();
+
+ /**
+ * Get the map of arrays of Entity Identifiers that are trusted to represent the accreditation authority for Trust
+ * Marks.
+ *
+ * @return trust mark issuers
+ */
+ @Nullable public Map<String, List<String>> getTrustMarkIssuers();
+
+ /**
+ * Get the map of Owners of the Trust Marks.
+ *
+ * @return trust mark owners
+ */
+ @Nullable public Map<String, TrustMarkOwner> getTrustMarkOwners();
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/EntityStatementPayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/EntityStatementPayload.java
new file mode 100644
index 0000000..673b47d
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/EntityStatementPayload.java
@@ -0,0 +1,41 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import java.util.List;
+
+import com.nimbusds.jose.jwk.JWKSet;
+
+/**
+ * Entity Statement payload claims as defined by the OpenID Federation 1.0 Section 3.1. These claims MUST or MAY appear
+ * in both Entity Configurations and Subordinate Statements.
+ */
+public interface EntityStatementPayload extends BaseMetadataPayload {
+
+ /**
+ * Get the JWK set.
+ *
+ * @return JWK set
+ */
+ public JWKSet getJwks();
+
+ /**
+ * Get the critical claims.
+ *
+ * @return critical claims
+ */
+ public List<String> getCritical();
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/ResolveEntityResponsePayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/ResolveEntityResponsePayload.java
new file mode 100644
index 0000000..f1bb742
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/ResolveEntityResponsePayload.java
@@ -0,0 +1,41 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Resolve Entity response payload claims as defined by the OpenID Federation 1.0 Section 8.3.2.
+ */
+public interface ResolveEntityResponsePayload extends BaseMetadataPayload {
+
+ /**
+ * Get the array containing the sequence of Entity Statements that compose the Trust Chain.
+ *
+ * @return trust chain
+ */
+ @Nullable public List<String> getTrustChain();
+
+ /**
+ * Get the array of Trust Marks.
+ *
+ * @return trust marks
+ */
+ @Nullable public List<Map<String, String>> getTrustMarks();
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/SignedKeysetPayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/SignedKeysetPayload.java
new file mode 100644
index 0000000..e335b5d
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/SignedKeysetPayload.java
@@ -0,0 +1,33 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import java.util.List;
+
+import com.nimbusds.jose.jwk.JWK;
+
+/**
+ * Signed keyset payload claims as defined by the OpenID Federation 1.0 Section 5.2.1.
+ */
+public interface SignedKeysetPayload extends BaseExpirableSubjectPayload {
+
+ /**
+ * Get the JWK set.
+ *
+ * @return JWK set
+ */
+ public List<JWK> getKeys();
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/SubordinateStatementPayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/SubordinateStatementPayload.java
new file mode 100644
index 0000000..c4c38ea
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/SubordinateStatementPayload.java
@@ -0,0 +1,59 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * Subordinate Statement payload claims as defined by the OpenID Federation 1.0 Section 3.3. This class extends the
+ * list of claims defined by {@link EntityStatementPayload} with the claims that MUST or MAY appear in Subordinate
+ * Statements but not in Entity Configurations.
+ */
+public interface SubordinateStatementPayload extends EntityStatementPayload {
+
+ /**
+ * Get the map that defines Trust Chain constraints.
+ *
+ * @return constraints
+ */
+ @Nullable public Map<String, Object> getConstraints();
+
+ /**
+ * Get the map that defines a metadata policy.
+ *
+ * @return metadata policy
+ */
+ @Nullable public Map<String, Map<String, MetadataPolicy>> getMetadataPolicy();
+
+ /**
+ * Get the list of critical metadata policy operators.
+ *
+ * @return critical metadata policy operators
+ */
+ @Nullable public List<String> getMetadataPolicyCritical();
+
+ /**
+ * Get the fetch endpoint URL from which the entity statement was issued.
+ *
+ * @return fetch endpoint URL
+ */
+ @Nullable public String getSourceEndpoint();
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/TrustMarkPayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/TrustMarkPayload.java
new file mode 100644
index 0000000..d7097fb
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/TrustMarkPayload.java
@@ -0,0 +1,51 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import javax.annotation.Nullable;
+
+/**
+ * Trust Mark payload claims as defined by the OpenID Federation 1.0 Section 7.1.
+ */
+public interface TrustMarkPayload extends BaseExpirableSubjectPayload {
+
+ /**
+ * Get the identifier of the type of the Trust Mark.
+ *
+ * @return trust mark type
+ */
+ @Nullable public String getTrustMarkType();
+
+ /**
+ * Get the URL that references a logo for the issued Trust Mark.
+ *
+ * @return logo URL
+ */
+ @Nullable public String getLogoUri();
+
+ /**
+ * Get the URL referring to human-readable information about the issuance of the Trust Mark.
+ *
+ * @return reference URL
+ */
+ @Nullable public String getReference();
+
+ /**
+ * Get the Trust Mark delegation JWT.
+ *
+ * @return delegation JWT
+ */
+ @Nullable public String getDelegation();
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/TrustMarkStatusPayload.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/TrustMarkStatusPayload.java
new file mode 100644
index 0000000..eea3e52
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/TrustMarkStatusPayload.java
@@ -0,0 +1,40 @@
+/*
+ * 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.oidfed.metadata.payload;
+
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.BasePayload;
+
+/**
+ * Trust Mark Status payload claims as defined by the OpenID Federation 1.0 Section 8.4.2.
+ */
+public interface TrustMarkStatusPayload extends BasePayload {
+
+ /**
+ * Get the Trust Mark.
+ *
+ * @return trust mark
+ */
+ @Nullable public String getTrustMark();
+
+ /**
+ * Get the status indicator for the Trust Mark.
+ *
+ * @return status
+ */
+ @Nullable public String getStatus();
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/Metadata.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/Metadata.java
new file mode 100644
index 0000000..3db1dfc
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/Metadata.java
@@ -0,0 +1,83 @@
+/*
+ * 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.oidfed.metadata.payload.claim;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Metadata claim as defined by the OpenID Federation 1.0 Section 5.
+ */
+public interface Metadata {
+
+ /**
+ * Get the federation entity metadata.
+ *
+ * @return federation entity metadata
+ */
+ @Nullable public Map<String,Object> getFederationEntityMetadata();
+
+ /**
+ * Get the OpenID Connect Relying Party metadata.
+ *
+ * @return OIDC RP metadata
+ */
+ @Nullable public Map<String,Object> getOpenidRelyingPartyMetadata();
+
+ /**
+ * Get the OpenID Connect OpenID Provider metadata.
+ *
+ * @return OIDC OP metadata
+ */
+ @Nullable public Map<String,Object> getOpenidProviderMetadata();
+
+ /**
+ * Get the OAuth Authorization Server metadata.
+ *
+ * @return OAuth AS metadata
+ */
+ @Nullable public Map<String,Object> getOauthAuthorizationServerMetadata();
+
+ /**
+ * Get the OAuth Client metadata.
+ *
+ * @return OAuth client metadata
+ */
+ @Nullable public Map<String,Object> getOauthClientMetadata();
+
+ /**
+ * Get the OAuth Protected Resource metadata.
+ *
+ * @return OAuth protected resource metadata
+ */
+ @Nullable public Map<String,Object> getOauthResourceMetadata();
+
+ /**
+ * Get the map of custom claims.
+ *
+ * @return The map of any other claims not directly mapped.
+ */
+ public Map<String,Map<String, Object>> getCustomClaims();
+
+ /**
+ * Get all claims (standard and custom) in a map keyed by entity types.
+ *
+ * @return all claims
+ */
+ @Nonnull public Map<String,Map<String,Object>> getAllClaims();
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/TrustMarkOwner.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/TrustMarkOwner.java
new file mode 100644
index 0000000..5c08bcd
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/TrustMarkOwner.java
@@ -0,0 +1,49 @@
+/*
+ * 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.oidfed.metadata.payload.claim;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import com.nimbusds.jose.jwk.JWKSet;
+
+/**
+ * Trust mark owner claim to be used with a map of trust_mark_owners as defined by the OpenID Federation 1.0 Section
+ * 3.1.2.
+ */
+public interface TrustMarkOwner {
+
+ /**
+ * Get the subject.
+ *
+ * @return subject
+ */
+ @Nullable public String getSub();
+
+ /**
+ * Get the JWK set.
+ *
+ * @return JWK set
+ */
+ @Nullable public JWKSet getJwks();
+
+ /**
+ * Get the map of custom claims.
+ *
+ * @return The map of any other claims not directly mapped.
+ */
+ @Nullable public Map<String, Object> getCustomClaims();
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/ProvidedTrustChainResolver.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/ProvidedTrustChainResolver.java
new file mode 100644
index 0000000..26c0515
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/ProvidedTrustChainResolver.java
@@ -0,0 +1,32 @@
+/*
+ * 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.oidfed.profile;
+
+import java.util.List;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.shared.resolver.Resolver;
+
+/**
+ * A resolver that is capable of finding provided trust chains from the {@link ProfileRequestContext}.
+ */
+ at ThreadSafe
+public interface ProvidedTrustChainResolver extends Resolver<List<EntityStatement<?>>, ProfileRequestContext> {
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/TrustedRemoteEntity.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/TrustedRemoteEntity.java
new file mode 100644
index 0000000..e396cb1
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/TrustedRemoteEntity.java
@@ -0,0 +1,74 @@
+/*
+ * 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.oidfed.profile;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A trusted remote entity whose federation endpoints are exploited.
+ */
+public class TrustedRemoteEntity {
+
+ /** The entity ID of the remote entity. */
+ @Nonnull @NotEmpty private final String entityId;
+
+ /**
+ * Constructor.
+ *
+ * @param entity entity ID of the remote entity
+ */
+ public TrustedRemoteEntity(@Nonnull @NotEmpty @ParameterName(name="entity") final String entity) {
+ entityId = Constraint.isNotEmpty(entity, "Entity ID cannot be empty");
+ }
+
+ /**
+ * Get the entity ID of the remote entity.
+ *
+ * @return entity ID of the remote entity
+ */
+ @Nonnull @NotEmpty public String getEntityId() {
+ return entityId;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("entityId", getEntityId())
+ .toString();
+ }
+
+ /** {@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 TrustedRemoteEntity other = (TrustedRemoteEntity) obj;
+ return entityId.equals(other.entityId);
+ }
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/TrustedRemoteResolverEntity.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/TrustedRemoteResolverEntity.java
new file mode 100644
index 0000000..df10c4e
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/TrustedRemoteResolverEntity.java
@@ -0,0 +1,81 @@
+/*
+ * 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.oidfed.profile;
+
+import java.util.Collection;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A trusted entity whose federation_resolve_endpoint is exploited.
+ */
+public class TrustedRemoteResolverEntity extends TrustedRemoteEntity {
+
+ /** The trust anchors to be used within the API request. */
+ @Nonnull @NotEmpty private final Collection<String> trustAnchors;
+
+ /**
+ * Constructor.
+ *
+ * @param entity ntity ID of the remote resolver
+ * @param anchors trust anchors to be used within the API request
+ */
+ public TrustedRemoteResolverEntity(@Nonnull @NotEmpty @ParameterName(name="entity") final String entity,
+ @Nonnull @NotEmpty @ParameterName(name="anchors") final Collection<String> anchors) {
+ super(entity);
+ trustAnchors = Constraint.isNotEmpty(anchors, "Trust Anchors cannot be empty");
+ }
+
+ /**
+ * Get the trust anchors to be used within the API request.
+ *
+ * @return trust anchors to be used within the API request
+ */
+ @Nonnull @NotEmpty public Collection<String> getTrustAnchors() {
+ return trustAnchors;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("entityId", getEntityId())
+ .add("trustAnchors", getTrustAnchors())
+ .toString();
+ }
+
+ /** {@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 TrustedRemoteResolverEntity other = (TrustedRemoteResolverEntity) obj;
+ return getEntityId().equals(other.getEntityId()) &&
+ trustAnchors.containsAll(other.trustAnchors) && other.trustAnchors.containsAll(trustAnchors);
+ }
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java
new file mode 100644
index 0000000..bc1c25a
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * 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.oidfed.profile.config;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * Profile configuration for an OpenID Federation Automatic Registration.
+ */
+public interface OIDFederationAutomaticRegistrationProfileConfiguration extends OIDFederationRegistrationProfileConfiguration {
+
+ /** ID for this profile configuration. */
+ public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/automaticregistration";
+
+ /**
+ * Get claims set decoration type.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return claims set decoration type, or null for default
+ */
+ @ConfigurationSetting(name="defaultClaimsSetDecorationType")
+ @Nullable @NotEmpty String getDefaultClaimsSetDecorationType(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get claims set decoration type for a client with request-provided trust chain.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return claims set decoration type, or null for default
+ */
+ @ConfigurationSetting(name="providedChainClaimsSetDecorationType")
+ @Nullable @NotEmpty String getProvidedChainClaimsSetDecorationType(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationEntityConfigurationProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationEntityConfigurationProfileConfiguration.java
new file mode 100644
index 0000000..00bbae1
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationEntityConfigurationProfileConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * 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.oidfed.profile.config;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.OverriddenIssuerProfileConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+
+/**
+ * Profile configuration for an OpenID Federation Entity Configuration.
+ */
+public interface OIDFederationEntityConfigurationProfileConfiguration extends OverriddenIssuerProfileConfiguration,
+ OIDFederationProfileConfiguration, OIDFederationEntityStatementProducingProfileConfiguration,
+ OIDFederationResponseCachingProfileConfiguration{
+
+ /** OIDC base protocol URI. Section 4 is relevant. */
+ public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
+
+ /** ID for this profile configuration. */
+ public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/configuration";
+
+ /**
+ * Get the authority hints to be included to the entity configuration.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return authority hints to be included to the entity configuration
+ */
+ @ConfigurationSetting(name="authorityHints")
+ @Nonnull @NonnullElements @NotLive @Unmodifiable List<String> getAuthorityHints(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get entity statement lifetime.
+ *
+ * <p>Defaults to 24 hours.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return entity statement lifetime
+ */
+ @ConfigurationSetting(name="entityStatementLifetime")
+ @Positive @Nonnull Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get the lookup strategies for optional claims to be included to the entity configuration.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return optional claims to be included to the entity configuration
+ */
+ @ConfigurationSetting(name="optionalClaimsLookupStrategies")
+ @Nonnull @NonnullElements @NotLive @Unmodifiable
+ Map<String,Function<ProfileRequestContext,Object>> getOptionalClaimsLookupStrategies(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationEntityStatementProducingProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationEntityStatementProducingProfileConfiguration.java
new file mode 100644
index 0000000..b6344bd
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationEntityStatementProducingProfileConfiguration.java
@@ -0,0 +1,73 @@
+/*
+ * 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.oidfed.profile.config;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+
+/**
+ * Configuration common to OpenID Federation Entity Statement producing profiles.
+ */
+public interface OIDFederationEntityStatementProducingProfileConfiguration extends OIDFederationProfileConfiguration {
+
+ /**
+ * Get the bi-function for manipulating entity statement claims set.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return the bi-function for manipulating entity statement claims set
+ */
+ @ConfigurationSetting(name="entityStatementClaimsSetManipulationStrategy")
+ @Nullable BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>
+ getEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get entity statement lifetime.
+ *
+ * <p>Defaults to 24 hours.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return entity statement lifetime
+ */
+ @ConfigurationSetting(name="entityStatementLifetime")
+ @Nonnull Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return optional claims to be included to the entity statement
+ */
+ @ConfigurationSetting(name="optionalClaimsLookupStrategies")
+ @Nonnull @NonnullElements @NotLive @Unmodifiable
+ Map<String,Function<ProfileRequestContext,Object>> getOptionalClaimsLookupStrategies(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationExplicitRegistrationProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationExplicitRegistrationProfileConfiguration.java
new file mode 100644
index 0000000..7ddcb1d
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationExplicitRegistrationProfileConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * 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.oidfed.profile.config;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2FlowAwareProfileConfiguration;
+import net.shibboleth.profile.config.OverriddenIssuerProfileConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+
+/**
+ * Profile configuration for an OpenID Federation Explicit Registration.
+ */
+public interface OIDFederationExplicitRegistrationProfileConfiguration
+ extends OIDFederationRegistrationProfileConfiguration, OAuth2FlowAwareProfileConfiguration,
+ OIDFederationEntityStatementProducingProfileConfiguration, OverriddenIssuerProfileConfiguration {
+
+ /** ID for this profile configuration. */
+ public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/explicitregistration";
+
+ /**
+ * Get the enabled token endpoint authentication methods.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return enabled token endpoint authentication methods
+ */
+ @ConfigurationSetting(name="tokenEndpointAuthMethods")
+ @Nonnull @NonnullElements @NotLive @Unmodifiable Set<String> getTokenEndpointAuthMethods(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationProfileConfiguration.java
new file mode 100644
index 0000000..1e78118
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationProfileConfiguration.java
@@ -0,0 +1,23 @@
+/*
+ * 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.oidfed.profile.config;
+
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2ProfileConfiguration;
+
+/** Common interface for OpenID Federation 1.0 Profile Configurations.*/
+public interface OIDFederationProfileConfiguration extends OAuth2ProfileConfiguration {
+
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationRegistrationProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationRegistrationProfileConfiguration.java
new file mode 100644
index 0000000..9f7638b
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationRegistrationProfileConfiguration.java
@@ -0,0 +1,40 @@
+
+package net.shibboleth.oidfed.profile.config;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2ProfileConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+
+/**
+ * Profile configuration for an OpenID Federation profiles related to client registration.
+ */
+public interface OIDFederationRegistrationProfileConfiguration
+ extends OAuth2ProfileConfiguration, OIDFederationTrustMarkValidatingProfileConfiguration {
+
+ /** OpenID Federation base protocol URI. */
+ public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
+
+ /**
+ * Get local metadata policy to be merged into the federation policy.
+ *
+ * <p>Defaults to empty map.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return local metadata policy
+ */
+ @ConfigurationSetting(name="localMetadataPolicy")
+ @Nonnull @NonnullElements @NotLive @Unmodifiable
+ Map<String, MetadataPolicy> getLocalMetadataPolicy(@Nullable final ProfileRequestContext profileRequestContext);
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationResolveEntityProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationResolveEntityProfileConfiguration.java
new file mode 100644
index 0000000..b128cdc
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationResolveEntityProfileConfiguration.java
@@ -0,0 +1,56 @@
+/*
+ * 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.oidfed.profile.config;
+
+import java.time.Duration;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2ClientAuthenticableClientProfileConfiguration;
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2ClientAuthenticableProfileConfiguration;
+import net.shibboleth.profile.config.OverriddenIssuerProfileConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.Positive;
+
+/**
+ * Profile configuration for an OpenID Federation Resolve Entity.
+ */
+public interface OIDFederationResolveEntityProfileConfiguration extends OverriddenIssuerProfileConfiguration,
+ OIDFederationProfileConfiguration, OIDFederationResponseCachingProfileConfiguration,
+ OAuth2ClientAuthenticableProfileConfiguration, OAuth2ClientAuthenticableClientProfileConfiguration,
+ OIDFederationTrustMarkValidatingProfileConfiguration {
+
+ /** OIDC base protocol URI. */
+ public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
+
+ /** ID for this profile configuration. */
+ public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/resolve-entity";
+
+ /**
+ * Get cached error response lifetime.
+ *
+ * <p>Defaults to 5 minutes.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return cached error response lifetime
+ */
+ @ConfigurationSetting(name="cachedErrorResponseLifetime")
+ @Positive @Nonnull
+ Duration getCachedErrorResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationResponseCachingProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationResponseCachingProfileConfiguration.java
new file mode 100644
index 0000000..4adffb0
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationResponseCachingProfileConfiguration.java
@@ -0,0 +1,47 @@
+/*
+ * 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.oidfed.profile.config;
+
+import java.time.Duration;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.OverriddenIssuerProfileConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.Positive;
+
+/**
+ * Common interface for OpenID Federation Profiles producing cacheable response messages.
+ */
+public interface OIDFederationResponseCachingProfileConfiguration extends OverriddenIssuerProfileConfiguration,
+ OIDFederationProfileConfiguration {
+
+ /**
+ * Get cached success response lifetime.
+ *
+ * <p>Defaults to 5 minutes.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return cached success response lifetime
+ */
+ @ConfigurationSetting(name="cachedSuccessResponseLifetime")
+ @Positive @Nonnull
+ Duration getCachedSuccessResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationSignedKeysetProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationSignedKeysetProfileConfiguration.java
new file mode 100644
index 0000000..be67a0b
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationSignedKeysetProfileConfiguration.java
@@ -0,0 +1,32 @@
+/*
+ * 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.oidfed.profile.config;
+
+import net.shibboleth.profile.config.OverriddenIssuerProfileConfiguration;
+
+/**
+ * Profile configuration for an OpenID Federation Entity Configuration.
+ */
+public interface OIDFederationSignedKeysetProfileConfiguration extends OverriddenIssuerProfileConfiguration,
+ OIDFederationProfileConfiguration, OIDFederationEntityStatementProducingProfileConfiguration,
+ OIDFederationResponseCachingProfileConfiguration{
+
+ /** OIDC base protocol URI. Section 4 is relevant. */
+ public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
+
+ /** ID for this profile configuration. */
+ public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/keyset";
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationTrustMarkValidatingProfileConfiguration.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationTrustMarkValidatingProfileConfiguration.java
new file mode 100644
index 0000000..7a6dbfc
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/OIDFederationTrustMarkValidatingProfileConfiguration.java
@@ -0,0 +1,74 @@
+/*
+ * 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.oidfed.profile.config;
+
+import java.time.Duration;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.profile.oauth2.config.OAuth2ProfileConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+
+/**
+ * Profile configuration for an OpenID Federation profiles related to trust mark consumption.
+ */
+public interface OIDFederationTrustMarkValidatingProfileConfiguration extends OAuth2ProfileConfiguration {
+
+ /** OpenID Federation base protocol URI. */
+ public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
+
+ /**
+ * Get the mandatory trust mark identifiers required by this profile configuration.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return mandatory trust mark identifiers
+ */
+ @ConfigurationSetting(name="mandatoryTrustMarks")
+ @Nonnull @NonnullElements @NotLive @Unmodifiable List<String> getMandatoryTrustMarks(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get maximum lifetime for trust marks.
+ *
+ * <p>Defaults to one year.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return maximum lifetime
+ */
+ @ConfigurationSetting(name="maximumTrustMarkLifetime")
+ @Positive @Nonnull
+ Duration getMaximumTrustMarkLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get whether trust marks should be remotely validated.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return whether trust marks should be remotely validated
+ */
+ @ConfigurationSetting(name="remoteTrustMarkValidation")
+ boolean isRemoteTrustMarkValidation(@Nullable final ProfileRequestContext profileRequestContext);
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/AuthorityHintsLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/AuthorityHintsLookupFunction.java
new file mode 100644
index 0000000..e61a685
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/AuthorityHintsLookupFunction.java
@@ -0,0 +1,48 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationEntityConfigurationProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that obtains {@link OIDFederationEntityConfigurationProfileConfiguration#getAuthorityHints(ProfileRequestContext)}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class AuthorityHintsLookupFunction extends AbstractRelyingPartyLookupFunction<List<String>> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public List<String> apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationEntityConfigurationProfileConfiguration ofec) {
+ return ofec.getAuthorityHints(input);
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/CachedErrorResponseLifetimeLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/CachedErrorResponseLifetimeLookupFunction.java
new file mode 100644
index 0000000..5690b3f
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/CachedErrorResponseLifetimeLookupFunction.java
@@ -0,0 +1,46 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationResolveEntityProfileConfiguration;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationResolveEntityProfileConfiguration#getCachedErrorResponseLifetime(ProfileRequestContext)}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class CachedErrorResponseLifetimeLookupFunction
+ extends AbstractRelyingPartyLookupFunction<Duration> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
+ return Optional.ofNullable(getRelyingPartyContextLookupStrategy().apply(input))
+ .map(relyingPartyContext -> relyingPartyContext.getProfileConfig())
+ .filter(OIDFederationResolveEntityProfileConfiguration.class::isInstance)
+ .map(OIDFederationResolveEntityProfileConfiguration.class::cast)
+ .map(config -> config.getCachedErrorResponseLifetime(input))
+ .orElse(null);
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/CachedSuccessResponseLifetimeLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/CachedSuccessResponseLifetimeLookupFunction.java
new file mode 100644
index 0000000..dae48b7
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/CachedSuccessResponseLifetimeLookupFunction.java
@@ -0,0 +1,46 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationResponseCachingProfileConfiguration;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationResponseCachingProfileConfiguration#getCachedSuccessResponseLifetime(ProfileRequestContext)}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class CachedSuccessResponseLifetimeLookupFunction
+ extends AbstractRelyingPartyLookupFunction<Duration> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
+ return Optional.ofNullable(getRelyingPartyContextLookupStrategy().apply(input))
+ .map(relyingPartyContext -> relyingPartyContext.getProfileConfig())
+ .filter(OIDFederationResponseCachingProfileConfiguration.class::isInstance)
+ .map(OIDFederationResponseCachingProfileConfiguration.class::cast)
+ .map(config -> config.getCachedSuccessResponseLifetime(input))
+ .orElse(null);
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/ClaimsSetDecorationTypeLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/ClaimsSetDecorationTypeLookupFunction.java
new file mode 100644
index 0000000..0b67e20
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/ClaimsSetDecorationTypeLookupFunction.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidfed.profile.config.navigate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationAutomaticRegistrationProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationAutomaticRegistrationProfileConfiguration#getDefaultClaimsSetDecorationType(ProfileRequestContext)}
+ * if such a profile is available from a {@link RelyingPartyContext} obtained via a lookup function,
+ * by default a child of the {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class ClaimsSetDecorationTypeLookupFunction extends AbstractRelyingPartyLookupFunction<String> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationAutomaticRegistrationProfileConfiguration ofarpc) {
+ return ofarpc.getDefaultClaimsSetDecorationType(input);
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/EntityStatementClaimsSetManipulationStrategyLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/EntityStatementClaimsSetManipulationStrategyLookupFunction.java
new file mode 100644
index 0000000..325b900
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/EntityStatementClaimsSetManipulationStrategyLookupFunction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationEntityStatementProducingProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns entity statement claims set manipulation strategy from
+ * {@link OIDFederationEntityStatementProducingProfileConfiguration} if such a profile is available from a
+ * {@link RelyingPartyContext} obtained via a lookup function, by default a child of the {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class EntityStatementClaimsSetManipulationStrategyLookupFunction extends
+ AbstractRelyingPartyLookupFunction<BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> apply(
+ @Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationEntityStatementProducingProfileConfiguration ofesppc) {
+ return ofesppc.getEntityStatementClaimsSetManipulationStrategy(input);
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/EntityStatementLifetimeLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/EntityStatementLifetimeLookupFunction.java
new file mode 100644
index 0000000..4e323a2
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/EntityStatementLifetimeLookupFunction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.time.Duration;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationEntityStatementProducingProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationEntityStatementProducingProfileConfiguration#getEntityStatementLifetime(ProfileRequestContext)}
+ * if such a profile is available from a {@link RelyingPartyContext} obtained via a lookup function, by default a child
+ * of the {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class EntityStatementLifetimeLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationEntityStatementProducingProfileConfiguration ofec) {
+ return ofec.getEntityStatementLifetime(input);
+ }
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/LocalMetadataPolicyLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/LocalMetadataPolicyLookupFunction.java
new file mode 100644
index 0000000..2f3a8a8
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/LocalMetadataPolicyLookupFunction.java
@@ -0,0 +1,49 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidfed.profile.config.OIDFederationRegistrationProfileConfiguration;
+
+/**
+ * A function that obtains
+ * {@link OIDFederationRegistrationProfileConfiguration#getLocalMetadataPolicy(ProfileRequestContext)}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class LocalMetadataPolicyLookupFunction extends AbstractRelyingPartyLookupFunction<Map<String, MetadataPolicy>> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Map<String, MetadataPolicy> apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationRegistrationProfileConfiguration ofrpc) {
+ return ofrpc.getLocalMetadataPolicy(input);
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/MandatoryTrustMarksLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/MandatoryTrustMarksLookupFunction.java
new file mode 100644
index 0000000..b1a0783
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/MandatoryTrustMarksLookupFunction.java
@@ -0,0 +1,48 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationTrustMarkValidatingProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that obtains
+ * {@link OIDFederationTrustMarkValidatingProfileConfiguration#getMandatoryTrustMarks(ProfileRequestContext)}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class MandatoryTrustMarksLookupFunction extends AbstractRelyingPartyLookupFunction<List<String>> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public List<String> apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationTrustMarkValidatingProfileConfiguration ofrpc) {
+ return ofrpc.getMandatoryTrustMarks(input);
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/MaximumTrustMarkLifetimeLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/MaximumTrustMarkLifetimeLookupFunction.java
new file mode 100644
index 0000000..cb891e2
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/MaximumTrustMarkLifetimeLookupFunction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.time.Duration;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationTrustMarkValidatingProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationTrustMarkValidatingProfileConfiguration#getMaximumTrustMarkLifetime(ProfileRequestContext)} if such a
+ * profile is available from a {@link RelyingPartyContext} obtained via a lookup function, by default a child of the
+ * {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class MaximumTrustMarkLifetimeLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationTrustMarkValidatingProfileConfiguration ofrpc) {
+ return ofrpc.getMaximumTrustMarkLifetime(input);
+ }
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/OptionalClaimsLookupStrategiesLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/OptionalClaimsLookupStrategiesLookupFunction.java
new file mode 100644
index 0000000..e823bc0
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/OptionalClaimsLookupStrategiesLookupFunction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationEntityStatementProducingProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns optional entity configuration claims lookup strategies from
+ * {@link OIDFederationEntityStatementProducingProfileConfiguration} if such a profile is available from a
+ * {@link RelyingPartyContext} obtained via a lookup function, by default a child of the {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class OptionalClaimsLookupStrategiesLookupFunction extends
+ AbstractRelyingPartyLookupFunction<Map<String,Function<ProfileRequestContext,Object>>> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Map<String,Function<ProfileRequestContext,Object>> apply(
+ @Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationEntityStatementProducingProfileConfiguration ofecpc) {
+ return ofecpc.getOptionalClaimsLookupStrategies(input);
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/ProvidedChainClaimsSetDecorationTypeLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/ProvidedChainClaimsSetDecorationTypeLookupFunction.java
new file mode 100644
index 0000000..6c439e4
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/ProvidedChainClaimsSetDecorationTypeLookupFunction.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidfed.profile.config.navigate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationAutomaticRegistrationProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationAutomaticRegistrationProfileConfiguration#getProvidedChainClaimsSetDecorationType(
+ * ProfileRequestContext)} if such a profile is available from a {@link RelyingPartyContext} obtained via a lookup
+ * function, by default a child of the {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class ProvidedChainClaimsSetDecorationTypeLookupFunction extends AbstractRelyingPartyLookupFunction<String> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationAutomaticRegistrationProfileConfiguration ofarpc) {
+ return ofarpc.getProvidedChainClaimsSetDecorationType(input);
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/RemoteTrustMarkValidationPredicate.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/RemoteTrustMarkValidationPredicate.java
new file mode 100644
index 0000000..4f260f5
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/RemoteTrustMarkValidationPredicate.java
@@ -0,0 +1,43 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationTrustMarkValidatingProfileConfiguration;
+import net.shibboleth.profile.context.logic.AbstractRelyingPartyPredicate;
+
+/**
+ * A predicate implementation that forwards to
+ * {@link OIDFederationTrustMarkValidatingProfileConfiguration#isRemoteTrustMarkValidation(ProfileRequestContext)},
+ * defaults to true.
+ */
+public class RemoteTrustMarkValidationPredicate extends AbstractRelyingPartyPredicate {
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ return Optional.ofNullable(getRelyingPartyContextLookupStrategy().apply(input))
+ .map(rpc -> rpc.getProfileConfig())
+ .filter(OIDFederationTrustMarkValidatingProfileConfiguration.class::isInstance)
+ .map(OIDFederationTrustMarkValidatingProfileConfiguration.class::cast)
+ .map(pc -> pc.isRemoteTrustMarkValidation(input))
+ .orElse(true);
+ }
+}
diff --git a/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/TokenEndpointAuthMethodLookupFunction.java b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/TokenEndpointAuthMethodLookupFunction.java
new file mode 100644
index 0000000..7d1c806
--- /dev/null
+++ b/oidfed-common-api/src/main/java/net/shibboleth/oidfed/profile/config/navigate/TokenEndpointAuthMethodLookupFunction.java
@@ -0,0 +1,56 @@
+/*
+ * 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.oidfed.profile.config.navigate;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationExplicitRegistrationProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that obtains
+ * {@link OIDFederationExplicitRegistrationProfileConfiguration#getTokenEndpointAuthMethods(ProfileRequestContext)}
+ * if such a profile is available from a {@link RelyingPartyContext} obtained via a lookup function,
+ * by default a child of the {@link ProfileRequestContext}. That result is then transformed into a list
+ * of {@link ClientAuthenticationMethod}s.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class TokenEndpointAuthMethodLookupFunction
+ extends AbstractRelyingPartyLookupFunction<Set<ClientAuthenticationMethod>> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Set<ClientAuthenticationMethod> apply(@Nullable final ProfileRequestContext input) {
+ return Optional.ofNullable(getRelyingPartyContextLookupStrategy().apply(input))
+ .map(relyingPartyContext -> relyingPartyContext.getProfileConfig())
+ .filter(OIDFederationExplicitRegistrationProfileConfiguration.class::isInstance)
+ .map(OIDFederationExplicitRegistrationProfileConfiguration.class::cast)
+ .map(config -> config.getTokenEndpointAuthMethods(input).stream()
+ .map(ClientAuthenticationMethod::new)
+ .collect(Collectors.toUnmodifiableSet()))
+ .orElse(null);
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-dist/pom.xml b/oidfed-common-dist/pom.xml
new file mode 100644
index 0000000..7dc33f7
--- /dev/null
+++ b/oidfed-common-dist/pom.xml
@@ -0,0 +1,88 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.shibboleth.oidfed</groupId>
+ <artifactId>oidfed-common-parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>oidfed-common-dist</artifactId>
+ <packaging>pom</packaging>
+ <name>Shibboleth IdP :: Plugins :: OpenID Federation Common Library :: Distribution</name>
+ <description>Packaging for the Shibboleth OIDFed Java common library.</description>
+
+ <properties>
+ <checkstyle.configLocation>${project.basedir}/../resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
+ <dist.plugin.finalName>shibboleth-oidfed-common-${project.version}</dist.plugin.finalName>
+ <maven-dist-enforcer-data.version>2.0.1</maven-dist-enforcer-data.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <!-- Assemble -->
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <appendAssemblyId>false</appendAssemblyId>
+ <descriptors>
+ <descriptor>src/main/assembly/assembly-tgz.xml</descriptor>
+ </descriptors>
+ <finalName>${dist.plugin.finalName}</finalName>
+ <overrideUid>1000</overrideUid>
+ <overrideGid>1000</overrideGid>
+ <tarLongFileMode>gnu</tarLongFileMode>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>net.shibboleth.maven.enforcer.rules</groupId>
+ <artifactId>maven-dist-enforcer</artifactId>
+ <version>${maven-dist-enforcer.version}</version>
+ </dependency>
+ </dependencies>
+ <executions>
+ <execution>
+ <id>oidfed-common-enforce</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <jarEnforcer implementation="net.shibboleth.mvn.enforcer.impl.JarEnforcer">
+ <dataGroupId>net.shibboleth.maven.enforcer.rules</dataGroupId>
+ <dataArtifactId>maven-dist-enforcer-data</dataArtifactId>
+ <dataVersion>${maven-dist-enforcer-data.version}</dataVersion>
+ <dataKeyRing>${basedir}/src/main/enforcer/shibbolethKeys.gpg</dataKeyRing>
+ <parentPomDir>${basedir}/..</parentPomDir>
+ <tgzFiles>${project.build.directory}/${dist.plugin.finalName}.tar.gz</tgzFiles>
+ <checkSignatures>true</checkSignatures>
+ <checkDependencies>true</checkDependencies>
+ <listJarSources>true</listJarSources>
+ <checkM2>${net.shibboleth.maven.enforcer.rules.checkM2}</checkM2>
+ </jarEnforcer>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ </build>
+
+
+</project>
diff --git a/oidfed-common-dist/src/main/assembly/assembly-tgz.xml b/oidfed-common-dist/src/main/assembly/assembly-tgz.xml
new file mode 100644
index 0000000..4cf098a
--- /dev/null
+++ b/oidfed-common-dist/src/main/assembly/assembly-tgz.xml
@@ -0,0 +1,72 @@
+<assembly
+ xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+ <id>bin-gz</id>
+ <formats>
+ <format>tar.gz</format>
+ </formats>
+
+ <fileSets>
+ <fileSet>
+ <directory>src/main/resources/</directory>
+ <outputDirectory></outputDirectory>
+ </fileSet>
+
+ <!-- The plugin property file -->
+ <fileSet>
+ <directory>../oidfed-common-impl/target/classes/net/shibboleth/oidfed/</directory>
+ <outputDirectory>bootstrap</outputDirectory>
+ <includes>
+ <include>plugin.properties</include>
+ </includes>
+ </fileSet>
+
+ <!-- The implementation jar -->
+ <fileSet>
+ <directory>../oidfed-common-impl/target</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>oidfed-common-impl-*.jar</include>
+ </includes>
+ <excludes>
+ <exclude>*test*.jar</exclude>
+ <exclude>*javadoc.jar</exclude>
+ <exclude>*sources.jar</exclude>
+ </excludes>
+ </fileSet>
+
+ <!-- The API jar -->
+ <fileSet>
+ <directory>../oidfed-common-api/target</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>oidfed-common-api-*.jar</include>
+ </includes>
+ <excludes>
+ <exclude>*test*.jar</exclude>
+ <exclude>*javadoc.jar</exclude>
+ <exclude>*sources.jar</exclude>
+ </excludes>
+ </fileSet>
+
+ <!-- The dependencies -->
+ <fileSet>
+ <directory>../oidfed-common-impl/target/dependency</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+
+ <!-- The signing certificates -->
+ <fileSet>
+ <directory>src/main/resources/bootstrap</directory>
+ <outputDirectory>bootstrap</outputDirectory>
+ <includes>
+ <include>keys.txt</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+
+</assembly>
diff --git a/oidfed-common-dist/src/main/enforcer/shibbolethKeys.gpg b/oidfed-common-dist/src/main/enforcer/shibbolethKeys.gpg
new file mode 100644
index 0000000..6d8fc48
Binary files /dev/null and b/oidfed-common-dist/src/main/enforcer/shibbolethKeys.gpg differ
diff --git a/oidfed-common-dist/src/main/resources/bootstrap/id.property b/oidfed-common-dist/src/main/resources/bootstrap/id.property
new file mode 100644
index 0000000..ccf52fc
--- /dev/null
+++ b/oidfed-common-dist/src/main/resources/bootstrap/id.property
@@ -0,0 +1 @@
+plugin.id=net.shibboleth.oidfed.common
diff --git a/oidfed-common-dist/src/main/resources/bootstrap/keys.txt b/oidfed-common-dist/src/main/resources/bootstrap/keys.txt
new file mode 100644
index 0000000..53a8f43
--- /dev/null
+++ b/oidfed-common-dist/src/main/resources/bootstrap/keys.txt
@@ -0,0 +1,375 @@
+Henri Mikkonen PGP key
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBF46zL0BEACUeQllAAViSlyL8uFBCjlCXdH12GpDL9y8fubm+N50ofonIloA
+YLbJtETVrqpxfeh+SDiERbEG5W02fbM1y3wdSjef0jzAEP3PoXydv/SdNKvomvBP
+U7I9eALgHJI4Nkqzf8ggTrOBHcWbRIRGbVXFRhOE1Z86akmVz3fe3aQzddvzAS7I
+YYX0RxbKiNt8iaxUXUo+P1LopD9Zo2I1NTY8u27RuhtxBr5tnHnsuf38mzjG/l6U
+RzJ8qhHJr6D4E+MLqRo9ndTREOT/d1TeJUvQddXC59VEL75TrYCEc2v/NZ5m9fD6
+yg0+oqgyrQHmZhPVOqoJiz0lkd3rl7lUqCH9yjREr1H5PUchiuhBKBOogwtirqw3
+NMKH6bs0Bu6qUy5fIJRqjxKVv+6fOEty/xnp0xN7xoBEUPEt1M/V3ewwH1zhOwTo
+g4cr4zhTT9RNno3eM0eenEQYapQZ8dFmrNVmhvx9VJlshYGyakrxPwrF3coyC3hh
+HjWE9SzmoyGmmbRgvJVt//SqoGpDyaM+d1hPys9tX2N/E1TlwZiD2brWAtjr2K49
+NC9Skizw4qHAbphq4EMGCKzrp9ksnBvwZAY9JjL0JvdjAabqkyRFVh2Mpm5xSxbw
+d+Twryh5hXaT/EQXsKMC1WlQnIDREjHpm1UOXTzcsFPa9tEW8XUftPWbQQARAQAB
+tCZIZW5yaSBNaWtrb25lbiA8aGVucmkubWlra29uZW5AaWtpLmZpPokCVAQTAQgA
+PhYhBG0Y/WNwj8ygebaMzgJmkYOTVevKBQJeOsy9AhsDBQkHhh+ABQsJCAcCBhUK
+CQgLAgQWAgMBAh4BAheAAAoJEAJmkYOTVevKwWcP+gLrjnrNxqwEx7/Ly/KdjkGD
+0W7aMiQc8acvC9oo74/XXpAD0W1jkK/BXyLH1q/o5Lyjymmm6w7VvEWLSY1Q0+gC
+l+hUOqccH572767UrGEeZeJV8+tNhziTU2S7NagK2A0BelHoA3hIhfGmWLJ+ooJe
+HZXFCov4ThZOpGzu5d04dEYoOv2jVaWwnrjOBzoKcgws9J6RLX+6gOFhZ3Dh5Rxs
+UGhl0ZJuEBQCDT7X9jI4mHsA0Ngo27inb3gxfeCm/ziZhHDV2gZtl777dKVc/sQN
+fqGaRGVi1p37La6KKpfIA3KHRjGf4jfg17AQ1Ix+ZgRIpbPXb7fXQHtBElhIbbn/
+VR2CG0Jdchdc4UozelKU6WNsNlcMn3kfTNFosW7+gTiYEGSxZQC9ylSSl1s9oIFM
+dvk70u4AgTY6w+27TrTRuEpdARoNZG4NhBTJ8g0BkiX6cHVyc5ir5IOVpmewsxN5
+yLg0ed6OwpcK5V8SwGT60hgkkJp71OeBsnLzyzO3/YoI5GVAIgcwtdzptRUt0iL8
+GUccO3mO6Hm4EfJAZHFWRbxX3ITTfCzw4blbXURlIXkPefprptAYX2+rn/z4iC1F
+mJUANl+4WilKuPoAimKGDNi6CvlbckQW2i2i5gsoM3iMxRMsExoZUnoMpfY70Trg
+ToF/jwURMQSCsJnZvyQDuQINBF46zL0BEAD9AuFJ7J1R5AOW9OzFTRdyMh4bCOtt
+p761l0UmaW5tkgtmKH977E/xB+RhgXTTL7tqWZD3rAt+/uP/4/kAzO9WpaiRnFIC
+oZcE1O6BU4+jbl16PJRf20LOfZlsGT5nEmYvTGTIsZYcTalE+iNiFbK3ehe2MOeZ
+96GTH+r10zcOI6j0k8fKnkKzs1BeqdbgxBQlqOy4fBoS2tgGYHsqyH4/IHqfQbxM
+QPQPxgNE8WMh7CqA3jkOw6tNj/RmsQ1Y8qjVmyQjNFt5p49+UEx2lRkYHfSSQADd
+uCbs0D1ccyI3vlvIy5Hn+aLqKR7Y2LpLgCUkXqPWDNv/nTzvbIkbKy6ZNrDyiuq1
+7L/HOnE5nR964zR5fhEMTDBAi/TwT454xkNnnTHhvGKlP2VCe30J8z4O1XoCCy3r
+BFImgU0t10lpxnIXiZFu8GeFT2ddgLph8EHXk5M/IjrKGW9I1JV2HgWF5T13Izff
+k9dvHETijvGyFpFezJfjRuDP3dzPCsXR4FJJiClXm0S3H+bLYLf0rrWDQzPU3c14
+fdh7HIZsRaZIPM0PjM3as1DMjm5TtuZi089Q78Yi5WdEwivZlvPfVckvTJUGcWhe
+sYR6ynQ749ORLz8jjbrhT6DDkjjvzVCepRLsARKRAvVF+I00ddeH0JxvPjHpyyUc
+zhKXqTzD813SJwARAQABiQI7BBgBCAAmFiEEbRj9Y3CPzKB5tozOAmaRg5NV68oF
+Al46zL0CGwwFCQeGH4AACgkQAmaRg5NV68pV3g/0C7clD4qsIU3TOLMZcWRHzvgp
+Z+yhSf80B3TYPempR6aOntqkDWsqVmt7D4nIehdCHfVDyW+PF+Jf17iot7AfsrSy
+lTQsOKwMM5Rw05VfqKIZBlJsHnKUmprC3yDV0CdidC9Cq0pQdiVeHzvS7R9HmMPG
+da30HikBHiFsYMIS+1hJKa//X75ncKiPc8ypoM7O6HrtArXZiWRjLfpcHBBHbVea
+ixOotHM271C3KsWTqURgzCX+GrumMS7QvXnHq4xketuBsVD6X/rlHzLjxSE0p7Tj
+G/B2VV1WPkb+QgPDC/1rXIi6NMm9GE/tzbPXTcGHX2irHlvGvotg32vWwehRnqNF
+exLuW4t30p+8E27+l03kGILCNLhhAFjjjPp4Vza/E3ZaQprSVBr9gH1HwZKUTThM
+EqGmypTmvnmx3Kw6pG0tia4wdLSxfyZh5XltUnwVSqptWdvt5tlceMFJlGxvIuw1
+ubDr97aIVM7kME6E5D59IXDWnxkIbdoAtaeQegO2OeyvbffuKnnX0ogF4Gcu6Zed
+ap7nWr2LBEwN3S9+hDIrXfs3QMy3bZIPkVCo0ncwaJPZFIMWWqeUkkjTzOKbQ53P
+6REH0FGoCXOH3qTqbS3bPNmyD3TVtN8OwiUZsOr/zu9VdNqUW3oq+aix4tU00pu7
+A+i1fd0Gifis1HhBeA==
+=ObHY
+-----END PGP PUBLIC KEY BLOCK-----
+
+pub rsa4096 2021-02-25 [SC]
+ B5B5DD332142AD657E8D87AC7D27E610B8A3DC52
+uid [ultimate] Philip David Smart <philip.smart at jisc.ac.uk>
+sig 3 7D27E610B8A3DC52 2021-02-25 Philip David Smart <philip.smart at jisc.ac.uk>
+sig 3 B77C52EEC21771DD 2021-02-25 philsmart (gpg key) <philip.smart at jisc.ac.uk>
+sig 2 9A804E97D7079C77 2021-03-04 Ian A. Young <ian at iay.org.uk>
+uid [ultimate] [jpeg image of size 9378]
+sig 3 7D27E610B8A3DC52 2021-02-25 Philip David Smart <philip.smart at jisc.ac.uk>
+sig 3 B77C52EEC21771DD 2021-02-25 philsmart (gpg key) <philip.smart at jisc.ac.uk>
+sig 2 9A804E97D7079C77 2021-03-04 Ian A. Young <ian at iay.org.uk>
+sub rsa4096 2021-02-25 [E]
+sig 7D27E610B8A3DC52 2021-02-25 Philip David Smart <philip.smart at jisc.ac.uk>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGA3eNkBEADXiVQf1XEUSM9gB+eMAPj4zdjtd2tflJTNI5Q6sEB2ly2rbOyg
+Yo49DF6wytQSRABAJvRY36hHkK77mbRN5Usf8Rq09aGUEv0nvKO+JVT/P4w3tFva
+MmKWiYX775/yDE7B1vu4eb1RQCjrXaye91EpHES74VR8Kb0TVeNEQXGR5/h77Gcg
+obRog+CqxT9L33Fcs4d2C+8BLo4dOaAr29rmEGWRE576NM6wGvtzHdazipM9LvtS
+POGRid4HLYuWvW5WCyAfGbkOq8xreYBtB9gGSZ8iarmcYmN50/gz3Ux3sJA/xy7Y
+vMAYWtA8Rp5hFb39uoaEwHWz8IlnAbA11OfrEkt65dMJwQAX4w89aag/LqI2lrgG
+VEoDWCvkKOlsbJalZr/SgG+m1k/4gqfYYkM09PI7IRn0Cb8uLDdXpeBZd2SJXi9E
+gdDxsrKoi0IMUYQNv8stNa/5lTK7LJkqovpdL+1aHCYRex53Ln+y8RycNbxSGl5O
+CKtFGb9ydfMnbOVX4BJ1x83OOktIkbLpVeZIsaTmUEDQ9itWmXqQ/QoIgBVFRqh8
+bhaCs7y9UPF+WtnpRY7jm3/cSL6oUXax2tT5VoU3LLxTQf90ZdWrAyWEF8auBZAP
+FRXAD2HkS4rIWCuKi/GSH0v6ILu9KREFfViy9fZXFpvwvbz199CPlCkgLQARAQAB
+tCxQaGlsaXAgRGF2aWQgU21hcnQgPHBoaWxpcC5zbWFydEBqaXNjLmFjLnVrPokC
+TQQTAQgAOBYhBLW13TMhQq1lfo2HrH0n5hC4o9xSBQJgN3jZAhsDBQsJCAcCBhUK
+CQgLAgQWAgMBAh4BAheAAAoJEH0n5hC4o9xSz4EP9Arv6WSxxkBEVzGj2XRbXAOP
+U0G5KFJ1sviO9ZGotBnivHH4HWcusDqoyDvjwIYp9jycgtwGw4TuF917QPTfFwhb
+TYma22+wSZ6Sn+OcZr+dSjLg5Ki+6I2BZaS+m2MYcPehCl1ILbtcs83p7AZf2JU1
+IgejhJMsMg93G88ZkSOO0tOAuvBbrO+f/de5AtSIk9ense1OUz9dbjI7JK6idB0a
+1yoo5FjOuyRw11qEa7nP787jcdnh6gkcfRGK9CcYOWXXcfQSRYFx0Wz6qi76bonk
+fYioCGv7LOPvsRnrjyWnM0tukS2RrDSbOfEby6ma4ZsPAhxtOrwWYOYfmjpspNbh
+yPHL/qw0Bb+t+X+mBayRO3MJ1R4l5lU3cjXF/oSCxinkL4TfX5bJ+SuPmPb6cOO4
+eHBHCwTad0jy9CEQAFIwtQP1+5QpcAUQPEhHlztPPHe5hP3X8M0x0ILTEDrh29E0
+C0CP0aG9xTONBK1JnmWT7NSXDzk+BLokdbDbZs909+fJddlzPq72u0ubRUOgKNki
+eo5Vbg5aOsaCkC0QJOzabO7xbnlOlXlg8XkGnfO8mIZ6Q0M5oEyGSpBakYi0rfQo
+zjzKwflCvRTKEl8spTkPH45Dm7LZ+o5xWSN4P2NVTh46unfCyKxuSi1Prl2tQ6OG
++ke+A9zfGa0iHsXtT8aJAjMEEwEIAB0WIQSHQCCMDpP+yA7r+RO3fFLuwhdx3QUC
+YDeIGgAKCRC3fFLuwhdx3Q0JEACRK35MbZe3mD7uKWb8pXwTxHfngDDA4TgpVLXA
+Oqvmh+ISYN1RVJUdAdws/PsTS9NgWCD3YbN57G0jtCT3Q0kCAtvXNPLPgmKxk0au
+Y2K1xaJs9iDjXysbWLIOgKdw2hs8FrD2YvYCQfm/jTQeG4TEqVJTvxcyLKVGBwud
+Hg6coVsqz0iazwXiPBE1mLlxXi9mk2wv5a3SySPYbGGF37cXEvX1ZRYG721bSaXW
+EUlZDzd2s2iv0FyM7aXjGeI77x7Ri9vG+KcCFdfoBrYjo5tQ0nm/0mWQr+uakDKr
+T4JUmFnzDCzIZcMqeSRcGRgJ8aCN55TctKcjWUMwXSI4PIUu9XjeHgTmIZVFZzSo
+OZGSxiPUuZ8HSjwb1g5RtOejLX0Sd5FCuzDUtNQmS1BIe3ZpW0D3iWtVGr6FWETc
++Ks2Dwa8T2ZbaOFfsJauqm8l1zJYcV8d45V1ASLnyqaMJdYlctlu+6rwgCgIx9LV
+pKhnvVWEdxEVmyt5UMSZwXfNF3LXW6PFrE9GSSUWcpGwF/X3XuGLpNc8a/lF6RHA
+pbRuy5RN1v10feOqfpd8sFbvxQLuN3Xfo+HFjtjqF74BxASJm+2UePPeanIifRvQ
+Hd8NCGycYaNkdlkBQH3BLaTAD6pf25Hd2Iah3iRvY4gCPE0MoUhUERgXlfwd1L0H
+LpLd+YkCMwQSAQgAHRYhBF5tbq4Ww9p1RQshnJqATpfXB5x3BQJgQPZxAAoJEJqA
+TpfXB5x3JzoP+wQO4IfR4x1Fpd4i7P1YEHp1FWX9CiVkgt8KkND2QFv9jSA1VAIn
+zE0AJps33X3vtz4An0+oWEi1zVNSsg/ShhWVcEUsRuojFZmPjzFuDBzBVBRmqqw9
+p3xGFAcFlhtpIhU4xbRgw9mImNKBX5dzJxKzP+tQcAhY36LwI+aYOfMIWnBSWvpV
+se1+vgCLmVnVB3HtzByppRK1g3nxESXtJblgbW2KvP4wvp8FGLcGdIqS5y1AXTfD
+biaZcB+cHUDQMkICrHdiCtJBN5Ds1Rer7fF2GG89zPNyxi9ODVTOed/v3kf6Fd02
+Tz8N4FLvfvzB1CvoxHXOs2vWwx4CXd5KRcqlN0bXSbuNj6iN6mwyq6rFGJiB0i8G
+9ngK1JNjcyAzTrZofPvOXKFFsiJ7WpKaRI1VnjY97X6lxMfrOMeAiO3/dGpavsHs
+mSM1YVySu6T/GqzgKvzq8hH9NRTba2MBFcEeF9nFaa9v87AhY+HpYQkj4nM2Ie2X
+4IByGVHXR0tm7YXaxbgGnANjipmIKxQjf/81UHZgKO0hLYLt51CmiqYBe1RRcnb1
+oh3REIuOPZKW5HBoVkrgBQBODW7zh5brYMf+f+fiu79xvJ/kTBn3IzhZ4Ay9m4jC
+WVefvpLdE/SLz1YjchS8SWJBCxo/vMzg5NyFq4gBg5GqNKQMBMqAumUq0f8AACS4
+/wAAJLMBEAABAQAAAAAAAAAAAAAAAP/Y/+AAEEpGSUYAAQEAAEgASAAA/+EH5kV4
+aWYAAE1NACoAAAAIAAwBDwACAAAABgAAAJ4BEAACAAAACQAAAKQBEgADAAAAAQAB
+AAABGgAFAAAAAQAAAK4BGwAFAAAAAQAAALYBKAADAAAAAQACAAABMQACAAAABQAA
+AL4BMgACAAAAFAAAAMQBQgAEAAAAAQAAAgABQwAEAAAAAQAAAgCHaQAEAAAAAQAA
+ANiIJQAEAAAAAQAABuQAAAAAQXBwbGUAaVBob25lIDcAAAAAAEgAAAABAAAASAAA
+AAExMS40AAAyMDE4OjA3OjE2IDEwOjA3OjM4AAAfgpoABQAAAAEAAAJSgp0ABQAA
+AAEAAAJaiCIAAwAAAAEAAgAAiCcAAwAAAAEAMgAAkAAABwAAAAQwMjIxkAMAAgAA
+ABQAAAJikAQAAgAAABQAAAJ2kQEABwAAAAQBAgMAkgEACgAAAAEAAAKKkgIABQAA
+AAEAAAKSkgMACgAAAAEAAAKakgQACgAAAAEAAAKikgcAAwAAAAEABQAAkgkAAwAA
+AAEAEAAAkgoABQAAAAEAAAKqkhQAAwAAAAQAAAKyknwABwAAA+IAAAK6kpEAAgAA
+AAQ3MTAAkpIAAgAAAAQ3MTAAoAAABwAAAAQwMTAwoAIABAAAAAEAAACWoAMABAAA
+AAEAAADqohcAAwAAAAEAAgAAowEABwAAAAEBAAAApAIAAwAAAAEAAAAApAMAAwAA
+AAEAAAAApAUAAwAAAAEAHAAApAYAAwAAAAEAAAAApDIABQAAAAQAAAacpDMAAgAA
+AAYAAAa8pDQAAgAAACIAAAbCAAAAAAAAAAEAAAARAAAACQAAAAUyMDE4OjA3OjE2
+IDEwOjA3OjM4ADIwMTg6MDc6MTYgMTA6MDc6MzgAAAAKdAAAApMAAAhvAAAE+QAA
+KOIAAA/BAAAAAAAAAAEAAAGPAAAAZAXXBg4DvwPBQXBwbGUgaU9TAAABTU0AEwAB
+AAkAAAABAAAACQACAAcAAAIuAAAA+AADAAcAAABoAAADJgAEAAkAAAABAAAAAQAF
+AAkAAAABAAAA6AAGAAkAAAABAAAA8QAHAAkAAAABAAAAAQAIAAoAAAADAAADjgAM
+AAoAAAACAAADpgANAAkAAAABAAAAKAAOAAkAAAABAAAABAAPAAkAAAABAAAAAgAQ
+AAkAAAABAAAAAQARAAIAAAAlAAADtgAUAAkAAAABAAAABQAXAAkAAAABAAAAAAAZ
+AAkAAAABAAAAAAAaAAIAAAAGAAAD3AAfAAkAAAABAAAAAAAAAABicGxpc3QwME8R
+AgBHAkwCUQJVAlICSwJDAjsCLAIaAnMBzgCQAIgAiAB/AEsCUgJWAlYCTgJGAj4C
+MQIgAsoBjwB/AHkAgACBAIAATgJXAloCVgJOAkQCOAIlAg8CMwFwAHcAjACKAJYA
+jwBMAlYCVwJQAkYCOgIrAhIC4wGiAGUAkgCJAI4AjQCSAEoCUgJQAkcCOgIqAhYC
+9AFqAXgAdACRAIUAhQCMAIsASQJKAvYB+gC8AOIAjgGJAbEAcQBrAIMAeQB6AH4A
+gQBFAoEBTQDQALEA4QDiAL0ArgCZAIkAYwBoAHkAdQBuAEECBAF5ABgBtADHANUA
+uQCkAN0AyQCQAIMAfABqAGUAOwL2AHIAIgEEAb0ArACYAIYAuwDQAK8AXgBmAG8A
+eAAxAgkBUADvAIsAqwC/AJ0AbQB5AF8AZABvAHwAdAB1ACYC5gG3AHUAdgCuANwA
+DAHGAGIAYQBqAHEAdwBvAHAAHQIaAv8BtAGjAZ0BhwF4ATEBcwBtAHYAdQB0AGsA
+ZwAYAhkCDQL5AegB2gHFAagBcgGtAF8AdgBzAHIAagBoABUCGQIUAgkC/AHoAc0B
+sAGPARYBZgBmAHkAagBzAGYAEwITAg8CBAL4AeUBygGzAZsBfgHNAGEAcgBpAGsA
+YQAQAg0CCQL/AfQB4gHJAbMBoAGMAXwBUwEDAeAAcAByAAAIAAAAAAAAAgEAAAAA
+AAAAAQAAAAAAAAAAAAAAAAAAAgxicGxpc3QwMNQBAgMEBQYHCFVmbGFnc1V2YWx1
+ZVl0aW1lc2NhbGVVZXBvY2gQARMAAAU6zneKsRI7msoAEAAIERcdJy0vOD0AAAAA
+AAABAQAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAP///xsEAChy8//+2DAAAShH///v9
+AArWDwAAADsAAAEAAAAAZQAAAQA0MkRDQUE3RC0wMTU0LTRBNTktQjY2NS03QTZB
+QjZBRkM4OEQAAHE4MjVzAAA/1d8AD/+1AD/V3wAP/7UAAAAJAAAABQAAAAkAAAAF
+QXBwbGUAaVBob25lIDcgYmFjayBjYW1lcmEgMy45OW1tIGYvMS44AAANAAEAAgAA
+AAJOAAAAAAIABQAAAAMAAAeGAAMAAgAAAAJXAAAAAAQABQAAAAMAAAeeAAUAAQAA
+AAEAAAAAAAYABQAAAAEAAAe2AAwAAgAAAAJLAAAAAA0ABQAAAAEAAAe+ABAAAgAA
+AAJUAAAAABEABQAAAAEAAAfGABcAAgAAAAJUAAAAABgABQAAAAEAAAfOAB8ABQAA
+AAEAAAfWAAAAAAAAADMAAAABAAAAIAAAAAEAAA5QAAAAZAAAAAMAAAABAAAACwAA
+AAEAABWDAAAAZAAAWb0AAADZAAAArQAABJ4AAN2FAAABWAAA3YUAAAFYAAAACAAA
+AAH/4Q1XaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVn
+aW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBt
+ZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2
+LjAuMCI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5
+OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFi
+b3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIg
+eG1sbnM6bXdnLXJzPSJodHRwOi8vd3d3Lm1ldGFkYXRhd29ya2luZ2dyb3VwLmNv
+bS9zY2hlbWFzL3JlZ2lvbnMvIiB4bWxuczpzdEFyZWE9Imh0dHA6Ly9ucy5hZG9i
+ZS5jb20veG1wL3NUeXBlL0FyZWEjIiB4bWxuczphcHBsZS1maT0iaHR0cDovL25z
+LmFwcGxlLmNvbS9mYWNlaW5mby8xLjAvIiB4bWxuczpzdERpbT0iaHR0cDovL25z
+LmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL0RpbWVuc2lvbnMjIiB4bWxuczpwaG90
+b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtcDpD
+cmVhdGVEYXRlPSIyMDE4LTA3LTE2VDEwOjA3OjM4LjcxMCIgeG1wOkNyZWF0b3JU
+b29sPSIxMS40IiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOC0wNy0xNlQxMDowNzozOCIg
+cGhvdG9zaG9wOkRhdGVDcmVhdGVkPSIyMDE4LTA3LTE2VDEwOjA3OjM4LjcxMCI+
+IDxtd2ctcnM6UmVnaW9ucyByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+IDxtd2ct
+cnM6UmVnaW9uTGlzdD4gPHJkZjpTZXE+IDxyZGY6bGk+IDxyZGY6RGVzY3JpcHRp
+b24gbXdnLXJzOlR5cGU9IkZhY2UiPiA8bXdnLXJzOkFyZWEgc3RBcmVhOnk9IjAu
+NTEyOTk5OTk5OTk5OTk5OSIgc3RBcmVhOnc9IjAuMjM3OTk5OTk5OTk5OTk5OTki
+IHN0QXJlYTp4PSIwLjM3MSIgc3RBcmVhOmg9IjAuMzE3OTk5OTk5OTk5OTk5OTUi
+IHN0QXJlYTp1bml0PSJub3JtYWxpemVkIi8+IDxtd2ctcnM6RXh0ZW5zaW9ucyBh
+cHBsZS1maTpBbmdsZUluZm9ZYXc9IjAiIGFwcGxlLWZpOkFuZ2xlSW5mb1JvbGw9
+IjI3MCIgYXBwbGUtZmk6Q29uZmlkZW5jZUxldmVsPSIxMDAwIiBhcHBsZS1maTpU
+aW1lc3RhbXA9IjEzNzk5OTkyMTQ1MCIgYXBwbGUtZmk6RmFjZUlEPSI5Ii8+IDwv
+cmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpsaT4gPC9yZGY6U2VxPiA8L213Zy1yczpS
+ZWdpb25MaXN0PiA8bXdnLXJzOkFwcGxpZWRUb0RpbWVuc2lvbnMgc3REaW06aD0i
+MzAyNCIgc3REaW06dz0iNDAzMiIgc3REaW06dW5pdD0icGl4ZWwiLz4gPC9td2ct
+cnM6UmVnaW9ucz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94Onht
+cG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0
+IGVuZD0idyI/PgD/7QB4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAD8cAVoAAxsl
+RxwCAAACAAIcAj8ABjEwMDczOBwCPgAIMjAxODA3MTYcAjcACDIwMTgwNzE2HAI8
+AAYxMDA3MzgAOEJJTQQlAAAAAAAQOGix0QQ3mMOAExf+M5Hq4v/iAkBJQ0NfUFJP
+RklMRQABAQAAAjBBREJFAhAAAG1udHJSR0IgWFlaIAfQAAgACwATADMAO2Fjc3BB
+UFBMAAAAAG5vbmUAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtQURCRQAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmNwcnQA
+AAD8AAAAMmRlc2MAAAEwAAAAa3d0cHQAAAGcAAAAFGJrcHQAAAGwAAAAFHJUUkMA
+AAHEAAAADmdUUkMAAAHUAAAADmJUUkMAAAHkAAAADnJYWVoAAAH0AAAAFGdYWVoA
+AAIIAAAAFGJYWVoAAAIcAAAAFHRleHQAAAAAQ29weXJpZ2h0IDIwMDAgQWRvYmUg
+U3lzdGVtcyBJbmNvcnBvcmF0ZWQAAABkZXNjAAAAAAAAABFBZG9iZSBSR0IgKDE5
+OTgpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA81EA
+AQAAAAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAY3VydgAAAAAAAAABAjMAAGN1cnYA
+AAAAAAAAAQIzAABjdXJ2AAAAAAAAAAECMwAAWFlaIAAAAAAAAJwYAABPpQAABPxY
+WVogAAAAAAAANI0AAKAsAAAPlVhZWiAAAAAAAAAmMQAAEC8AAL6c/8AAEQgA6gCW
+AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQ
+AAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHw
+JDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp
+anN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TF
+xsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB
+AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUh
+MQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4
+OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWW
+l5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp
+6vLz9PX29/j5+v/bAEMAGBgYGBgYKRgYKTopKSk6Tjo6OjpOY05OTk5OY3djY2Nj
+Y2N3d3d3d3d3d4+Pj4+Pj6enp6enu7u7u7u7u7u7u//bAEMBHR8fMCwwUiwsUsSF
+bYXExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE
+xMTExP/dAAQACv/aAAwDAQACEQMRAD8A6KiiisiwpaSkJ4oAZK2xd3uB+fFP3CqL
+yJLbkSHG8HisFNVlKgHsNx9z7U7AdYWA6mkSRJOUIOK59NWaWHaqbpT949F/z7VU
+sbgR3u18qrZBBOMHt+FFgudLdzGC3eQdR/U4plndfaolcD6//WqvqhB0+TB5wD+R
+qvZ3QWAMdqgDCrnGcUAbmcVnS6gkc/2cAljtxj/aP9KgbVI8HjOOuCD+nWuc84rc
+iYHgMDn2z/hQkI7v2pFO5Q3rVR7qMRiUHgdacJ44lSNjzgY96Qy3RTA2VBNOBBGR
+3oAWiiigAooooA//0OiopKM1kaDZHCLk1z099I26ZT8oO1R+maXV7jcwiQ9B/Osm
+dgifZ+pRv8/zppCbFRv3ZllJI6KM9exqkW5zQWPTtTT61ZID5eRTyc1FnsacB2oE
+S+dKy7C7Y9MnH5U4TybPKzwOKreop49aBjt1OzmmH1pM8UATLM4XGSRxwfbpVn7U
+7yq7HJFZ465pwPNKwHYpdA5T0J/KpLScSbVB6Er+VctDK6knOR3rf0kFsue24/mf
+/rGpaKNyiiikAUUUUAf/0ehqmJ1G8ucYNW6x9RiIUzL34NYmhi6g+Z1kHBIz+dUC
+xJyetSzyGRuar7TWiIF69KNnekwRzTstTAYRSZqTBNBQ0AMJzQDinbDThEx7UBYY
+abVjyW9KTyW9KVwsQZpRTzGR1pmKYE0Z5ye9dZpGDbkjjnH5Vx4OK1dMuxDON5+U
+qQf5j8uaTGdlRTEdXUOpBB6EdKfUDCiiimI//9LeNU7xglu7E8AZq2TgZrldQuHe
+Z4g2VHGMYrJFsy2bJzihVJpwXJwOSa1YLcKMnrVN2ElcopbO9WksfWtFVAqUCocj
+RRKK2SCpfsielXRS0rlcqKi2kY6iphboOcVKM07mlcLFfyUHamGBSelWqaaLhYov
+aKRWbPZsvK1v1G6g1SkJxOTIIODQDg1q3duPvLWSwNaJ3MmrHW6PdRmEWxPzr0Hq
+K3M159BKY5klH8JBr0BTkAmkwQ6iiikM/9PTvLgW0Blxkjp9a493MjtK3Vjmt7WJ
+cRrF/eOfyrnh0rOJTLNsmW3GtpRxWdaKMVpgVMjSI4YpwpBTuaksdSjNIM04fWkM
+MGlwaMn1pOfWgQvNNNLk0mRQMbSGlyKQ0wK8yBl5rAnj2ua6RhkVh3Yy2auJlNFF
+CQcjgiu50+c3FqjnqBg59a4auo0OUmN4ic4OQParZmjfopKKgo//1K+sHM6DttNZ
+ArW1b/Xr/u1kjk1C2KZrWowuavCq0K7EAqcVmzVD8mnAtTBUlIoUE08ZpoFSUhiZ
+PtSZNLSgcUANyaTNPxSGgCPiilNJTAQ8isW8Ta2fWto1QvUDRFv7vNVEiS0MI8Gt
+fRiVvBjGMHP44rJYd60dLP8Apkf41ozE7SiiioKP/9WPWIsFZfwrHiGZFrodYXNs
+rf3WH68Vg2g3TAemTWa2L6mwtOLBetQySCMe9U/OYnJ61Ni7mh5oAyaVbhM4yKzy
+0j9ttHk9807BzGykit0qUc1kRqyHg1pRvxSaKTJsU7GBTAc04nApDIJZ1j6mqhvF
+PXipZVDnmqbxxCqSJbZbWZW6HNO8wd6zeF+4/wCdG6T6j2osHMaoORUTjcpHrVaO
+Q9KtZyuaQXOdbgkelWdPYLdxk9N39Krz8SsPeruloHvVHpk/lWhkdoKKQcDFFQM/
+/9a9frvtJB7VzliMyk+1dDfcwe2awrFSJGz2H9ayWxZYkXcxzUJB3bE61dI71Sfc
+WITigqxII4x985PuaUxREfKPxFUriLYFI5z3qOEFpEVMjOAfr3p2DmLoZozg8irU
+cmelQFSjbJOR605UKHjpSGa0RyKbI2KjibApshzUlFaRz0FQAKT83NWGUDk9aqyR
+M0bt3A4FUInDxDg4pTGpG5P0rHABUrjnOc+3pWgsLxojocNjkU7EqVyyq7utTKMD
+FNiORzU2Kkqxg3sZSYn15p+nytFdK6jJwR+dT6kn3X/Co7GMjMntirvoZ21Ostrg
+XAPGCvWrOKzNNHDmtSpG1qf/1714u+3YfSsm2Qo0hPritqbmJvpWXGeD9ayNUPIz
+ULJg5qegipLsVmAYYYZFIixocooB9asbAaXZTuFiH8KDyamI2iowKAJlHFBGacvS
+jvSKIskcUzOO1TY5p/lg8igViqI06hFz64qTGal2CnBR2p3FYaq4p9OApp4pDsU7
+5N0Deo5FNtVAiVfQCprk/uW9xinRrsRVPYVXQlLU0LBdqt9a0c1UtR+7OPWrVBMt
+z//Q05P9W30NZKHlhWs43KR6isjOGH5GsTVE1LTAafmkWhRS02jNBQjc02lagDNA
+iRTR3p6IO9DAUDEI70vOKapwcGpCOKAQ2lFNGaXNAx1MY0uaYxoJZBMN21PU5/Kp
+B8zZ/hFAXcTnpjFTKuflUfSmJGjajEP1JqxTUXYgT0FOpmbP/9HUNZlxHh9/Y1pG
+oyAeDWRpczgafUfQkU8Ui0OpV5NIKkXikURyEAiq3musmMZX1qw+D1qttweKaJZZ
+E1RyySkfusZ9TQExyakCfLntTC4kbMRhutWx0quuF6VMrA1LGgIppqWojQMKYadT
+DQIegOOAav28LKd78egotP8AUj6mrVUQ5dB1FJRQSf/S0mplPamVkWUJRiQimipb
+gYYN68VEDSZaJBSs2OlNB4zULS7OTRYdxSSacAOpqobjJwoNIJjn5lNOwI0D83Q0
+uVVdpNZ3m85UGnrI/XaaLFWLwIo6dKo7pT2FPDzZwoosJo0FbtQ1VkZs89asMcgU
+mhXGmm0tNwScDvQM1LcYhX86s1Eo2gKO3FPFMyHUtJRQB//T0mqOpDUZrIsilXeh
+HfqKog9q0aoyrsfI6GgaYqHsaYyAtzSr60ueaRRGVU9aQIOxqQgmmmLPTNBSY4Lj
+0p2D6ioPJb1PFOWF/wC8aY+ckCZ6mngBeBTVhYHrUoAX60hOQ0rzmnseKQ0hPGKC
+RCeKmtk3Sbz0X+dVuScVJDfQxO0Eg27OrdqaQmzXpwpikMAw5Bp4oJHUUlLQB//U
+0jURqQ1FWRYVFIocbTUlNNMCiPlO09RS5BNRswkd8fwnFR7iODSsO5aB4p2arBhU
+qsOlKw7k1ANN3cUAgcGgZLnim5pu4Gk3CiwXH0xjxURkycClCnrTsTcmQdzWLc/6
+9z6nH6VuqOM1h3IzJNzjkH9BVRCWxY0y6dbgK7Eq/BB9exrqhXKWMaYfJBJPTuPQ
+j6GunQkqC3Xv9aJEolpaZmlzUjP/1b5php1MNZFiVBOxWMlcZ7ZqY1UuwDGc+lNC
+ZnWZwHU9c1Zcd6o2f33rRbpTe41sVyvpSh2FLTT3oAeJDS+YfSoqUUAP3t0FKAzd
+aFqwoFACKoA4qULk4ph61OnSpGhTwMVh3OfMYrxmtlu9Y1x/rDQhsk09gsyhuQeC
+PWukiJBaNjll4P19axlAFohHtWhbH959VH9a0lsZrcv0tJRWRR//2YkCTgQTAQgA
+OBYhBLW13TMhQq1lfo2HrH0n5hC4o9xSBQJgN4HxAhsDBQsJCAcCBhUKCQgLAgQW
+AgMBAh4BAheAAAoJEH0n5hC4o9xSm/AQAM1VQ6ulVGptsosNJYB+HigxIMZiXtIn
+tddo1oJ2HzucgHGF2VJemu5m2NxKrckNQdlO5AvYiD3cGsx1GCcUCqrVJnpVSzn0
+x2vAUd5Iu5AO6VLuWfz6lLzLYFmR7le1eEVV9PWpiFo5JmhCxFlmzbJ/LXg2rm+b
+gGwYg9PaW4zeJR721BE0YYzAdiiyih0oYQqm1rIpp5+rzHL34UeajNU8krcViWDv
+Q6qSKnkk4RQzzWWo5ByjDxB9UBF5eX1Ls/CyFzCpsNaPooKAxC1MQy53GrQIN4Wq
+Y73BMsfEutMvdWxgL5/zWKIS5Fd+hD9wTtOotu00PzamCq+5CnsggILpJenrHWSR
+6xoSgBL6iYjAq1DaLvGUv8/+gnblLkdh3A9+lCqqb45yAbBlZS4w12Jmfov+WKLS
+e464WTG0VTySkShhrwHKWkekXzCusx8nIow1FKo257N97fMLJFWRlBhCHcgtoYmq
+3a49CUndqAmM9MT1/8RX21xUq/vI3aTSXqnjgo+y5EMKNM6Q8u0KgVvxzNRyoyh6
+cJWgW+lXM55X8M1SDBruQBfTF2lnJbxq02hXyuDhWbtuhLCs01z3AxO42PZVuwqI
+/U7chZGzC2e02DPY0TVwbGJU6SEKg6wrZEeAzH7igRWR2AHGUEX0AzEkZD72mvZZ
+TMtW3ci6/EGZiQIzBBMBCAAdFiEEh0AgjA6T/sgO6/kTt3xS7sIXcd0FAmA3iFkA
+CgkQt3xS7sIXcd26xQ//dvjcgYkVg0KdAUQWE0KWFwHgW5mJWyITZs5PcwkArIGh
+pJCns9CRq5EGwGQuLWvhWBAloH+qFI83ydptPoxztZoy7keK1tCdDTNhk2RLhp98
+4CP5oObh6xgpuMQ1hVtwwdhOErGpU/auHgIr4qtScvsfLlhoXHCttzvnBkgiGKi+
+bn7hH+mGS6XCDDTXggh4s3wRZ/snaqy8WcaprfK3dw5Tpgck0pexikVcyI6sbruu
+euGwbUZ5JwuFNnRQzmjPqJk6ZC3tYqPslZFBKi83HXq4T2liTSI2m8eK/gWU512G
+T4HeLlkiSMLYOJeX+gKI25StkzXBqQoVKQJVc6RG8wdv34hSdFT0h1T6xtGKmb6y
+W0c855yFrp2yefSS9+R3DFxBEqo7YtGF1J53v87GBeYnFMRw6vZnon+Kurttn8ah
+gZP0veyb1JtCgBWAS6HVb2q6k/Uw+nt++NAZbr69rtnyzyrMNVvMnuM/L1diYfD0
+bncHd1Oj8KTHmFj7J9He2ytelfB/MFUmnkPWtAugPtnQMTGPYyb4VsOF53QsR0+8
+bJGGI7M3tk/C03sXGfMpYMUyKK7uyGWbKg97iKXQLIoHaI3zbIYNUMh//JspxCqg
+14GbQx8yaLDdyeORn8wmgsVy9qNEsc/PHJO8644KNZJM9x1fDihztBsRbRzwjVaJ
+AjMEEgEIAB0WIQRebW6uFsPadUULIZyagE6X1wecdwUCYED2egAKCRCagE6X1wec
+d5gKD/42YYeUFgaQDABPG45afoqwyP8H1xDi6W5F47wVFwI1GdSBmIqKGhNONwYS
+toeFKcX1ed0gBbFVkwaWEFKCeo0VjWcLYE1YaxL8ybo+iVrFfd/oEHyf414uvnur
+4jNVwqUmtYQMaLlXq2a9tVAGNtsgqsZKEYBanWDXm24z5l2rDGxLlVi7xTGU3pZE
+LfTF3HvZlN3l/ggagXF+2ocT5O0vTX1OO8x0lknTqhbjTJfi+ObJxV4DyE0ifQyh
+3smBUyxZwCcF9MhRKYnEcf8gbsCPDLoiwCGz0o7cKz2blm7NsTgxZXoS0HRx0/wy
+QHS+6mIDuH1gMLeEvJUql7cdKqGAbG5QD9xGPoW6PiTpFTtVusjxD4dIvil5oth5
+r9btVNtoSysEuL13BjBkrO4JuqVZvEdHFGjNE00q6JrI/SpeBZrRR/YZvLCgUZ9J
+w9S6g/usazLEAnOs2EH37yMFA3h3Jr+8l31CkGWBlbLHWc/6ufuyYo4F98TJ7WRV
+vq2RbAMziHMkDH47iMt2PkycLhqPplzbTyVVS/2r1u1d8vfhWlznASuT0/d6cpi0
+H9L/nqe2Mkst32e/X7jr54lSfURmKC+plNn4QQY61DcaUDw5UsMvl/ieDg7OqBeI
+JVgitbDLfIC15B5oRHalAmBOE9OulvvnqxZCHdQ63XunRIbvi7kCDQRgN3jZARAA
+uiPmuwrDqHm5siU9XwHIE5XpzwQv03zgZkwXGKMDcLvPbu5bDpSKvSsRHraaIDcC
+YszGCJJI7usAxrFDeBOo1i4QlVnNcZRpd7Kt4VkZPtKovsfWnDC5qePANEMV6Qjl
+R/wrZ+gmmRwUcC6Nn7TC2IIRfZ7Z5DpFpiCxVI+Q6AdzX4L7mHA6ElWy1XCg1rg+
+SJNSA250DxnavL4jHr+ZjdHmjTIMZCKpAv+7JkEmwrvBCkNuOx0/v6zpBO7QUknU
+3t3lYoKzvBnJbrlU2JgU9OjmjmOfONtcz9e1CVbaBUH8ksmQ/XHOZwBtPcj3Ir54
+sAOUfITzD8gHU07SxAm1vKl1MOEtGPkEFLv63mbvprHRelZoBVaO38Z1s0mnukSE
+veg8vOTSc/JekE9L7AVuEyUcW0b+bpxqkdsjw8PJNETdCQRQZasXRMTXBc9eXnqJ
+KJZWgDduim6zVBoWBf4VZUBcMuhjZ61VAD3mHe/0qF03oEPohqc7iHx9y6dCwM/A
+m4Pa4vVqnBpcjtnjxeyxBiJr9MOMhBR6owWo1tij3v2slzpzYF+7qBLkOTpWDC/X
+ANzOLMhmWlR/cjmatUwVGrgfVGINp3U2nR87+2dcKQwryf2oSYqQ4CckTYeImCcW
+MpZk5/q1rmXDp9wrpRJxXm6C7VQ0uOjab0a/4h9WZAUAEQEAAYkCNgQYAQgAIBYh
+BLW13TMhQq1lfo2HrH0n5hC4o9xSBQJgN3jZAhsMAAoJEH0n5hC4o9xSR3MQAK41
+FXiGu9pBsGkDt6O1VsLXV7oRQtJdv5HzU2tgnSCMlGPt25dEWW1ZstSiJ7+w+oiZ
+nNISl+GQ2AzEbAxQw1wgVQiODNicvNwUOI9IIwaB1/ldpRLupH//OehO1zUvDIjb
+eDeIugc8jk3SanGan8oI5kIKG3bardVk3Vuts1tuaf2xRTrmR42kCaNIdWDYrYr6
+6VLXF+1nZPQr72XHal4gyrfA7EELgLcMCINehrxsPnwRPqru080tefwNIdtlLhl5
+keYuQ5qeBtXfFlYGpv80NGZg91o6LSiKTxBNHiBlACDwsOZ8yse5TaqRAwiheWQc
+WiZySqab+UmP1p6SvErDxO3zdCqv0GyA5CLrXW8LpZCVNzJPuTzV/Y68ozgNKmqx
+Fkw3ExWx/IIYf/dgRbbwhHnlPd2TDyL7btoFluEig6bKvBAkzKSRRMC9G+z+dcZl
+EqtDt2F52QyOm4iYZuXO9vCsVGuwy+2mWyB9EaS3HWctt6l8s3jMmvM+9Q9vrOjF
+J3O04ihMRvb/H47jCjDhID1rLR3umqgT2mLzR25GPrWQG83F9Aop3vIunTpC2hta
+8neA5s805aHc71bUsukzGItlzy2v9woWflZS4tayEPumasRrixQQ/QI4zjVAb7uF
+nWYitJKCbFCTTHqWL0ER7GbxvKXlzBZURQrVQaQ9
+=TFGe
+-----END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file
diff --git a/oidfed-common-impl/pom.xml b/oidfed-common-impl/pom.xml
new file mode 100644
index 0000000..2558cd5
--- /dev/null
+++ b/oidfed-common-impl/pom.xml
@@ -0,0 +1,146 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.shibboleth.oidfed</groupId>
+ <artifactId>oidfed-common-parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>oidfed-common-impl</artifactId>
+ <packaging>jar</packaging>
+ <name>Shibboleth IdP :: Plugins :: OpenID Federation Common Library :: Implementation</name>
+ <description>Implementation classes for the Shibboleth OIDFed Java common library</description>
+
+ <properties>
+ <checkstyle.configLocation>${project.basedir}/../resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
+ <automatic.module.name>net.shibboleth.oidfed.impl</automatic.module.name>
+ </properties>
+
+ <dependencies>
+ <!-- Provided dependencies -->
+ <dependency>
+ <groupId>${oidfed-common.groupId}</groupId>
+ <artifactId>oidfed-common-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-crypto-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-crypto-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-profile-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-metadata-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-metadata-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-authn-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${shib-profile.groupId}</groupId>
+ <artifactId>shib-profile-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-admin-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-admin-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-messaging-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-security-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-profile-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${shib-shared.groupId}</groupId>
+ <artifactId>shib-support</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>content-type</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.minidev</groupId>
+ <artifactId>json-smart</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>oauth2-oidc-sdk</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-profile-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/OIDFedCommonModule.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/OIDFedCommonModule.java
new file mode 100644
index 0000000..13c86c6
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/OIDFedCommonModule.java
@@ -0,0 +1,37 @@
+/*
+ * 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.oidfed;
+
+import java.io.IOException;
+
+import net.shibboleth.idp.module.IdPModule;
+import net.shibboleth.idp.module.impl.PluginIdPModule;
+import net.shibboleth.profile.module.ModuleException;
+
+/**
+ * {@link IdPModule} implementation.
+ */
+public final class OIDFedCommonModule extends PluginIdPModule {
+
+ /**
+ * Constructor.
+ *
+ * @throws ModuleException on error
+ * @throws IOException on error
+ */
+ public OIDFedCommonModule() throws IOException, ModuleException {
+ super(OIDFedCommonModule.class);
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/OIDFedCommonPlugin.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/OIDFedCommonPlugin.java
new file mode 100644
index 0000000..9cfbc51
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/OIDFedCommonPlugin.java
@@ -0,0 +1,49 @@
+/*
+ * 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.oidfed;
+
+import java.io.IOException;
+
+import net.shibboleth.idp.module.IdPModule;
+import net.shibboleth.idp.plugin.impl.FirstPartyIdPPlugin;
+import net.shibboleth.profile.module.ModuleException;
+import net.shibboleth.profile.plugin.PluginException;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * Details about the OIDC OP plugin.
+ */
+public class OIDFedCommonPlugin extends FirstPartyIdPPlugin {
+
+ /**
+ * Constructor.
+ *
+ * @throws IOException if the properties fail to load
+ * @throws PluginException if other errors occur
+ */
+ public OIDFedCommonPlugin() throws IOException, PluginException {
+ super(OIDFedCommonPlugin.class);
+ try {
+ final IdPModule module = new OIDFedCommonModule();
+ setEnableOnInstall(CollectionSupport.singleton(module));
+ setDisableOnRemoval(CollectionSupport.singleton(module));
+ } catch (final IOException e) {
+ throw e;
+ } catch (final ModuleException e) {
+ throw new PluginException(e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/AbstractSignedJWTResponse.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/AbstractSignedJWTResponse.java
new file mode 100644
index 0000000..4a424f4
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/AbstractSignedJWTResponse.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.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.Response;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * An abstract class for the responses containing a signed JWT / entity statement.
+ */
+public abstract class AbstractSignedJWTResponse implements Response {
+
+ /** The JWT included in the response. */
+ @Nonnull private final SignedJWT jwt;
+
+ /**
+ * Constructor.
+ *
+ * @param statement JWT
+ */
+ public AbstractSignedJWTResponse(@Nonnull final SignedJWT statement) {
+ jwt = Constraint.isNotNull(statement, "Entity statement cannot be null");
+ final JOSEObjectType type = jwt.getHeader().getType();
+ if (!getJWTTypeHeader().equals(type)) {
+ throw new ConstraintViolationException("Unexcepted type header (" + type + "), expected "
+ + getJWTTypeHeader());
+ }
+ }
+
+ /**
+ * Get the JWT included in the response.
+ *
+ * @return JWT
+ */
+ @Nonnull public SignedJWT getJWT() {
+ return jwt;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean indicatesSuccess() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public HTTPResponse toHTTPResponse() {
+ final HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_OK);
+ httpResponse.setEntityContentType(getHttpResponseContentType());
+ httpResponse.setContent(getJWT().serialize());
+ return httpResponse;
+ }
+
+ /**
+ * Get the content type of the response.
+ *
+ * @return content type
+ */
+ @Nonnull protected abstract ContentType getHttpResponseContentType();
+
+ /**
+ * Get the JWT type header.
+ *
+ * @return JWT type header
+ */
+ @Nonnull protected abstract JOSEObjectType getJWTTypeHeader();
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/EntityConfigurationResponse.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/EntityConfigurationResponse.java
new file mode 100644
index 0000000..d2ce06f
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/EntityConfigurationResponse.java
@@ -0,0 +1,55 @@
+/*
+ * 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.oidfed.messaging.impl;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.common.contenttype.ContentType;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+/**
+ * Response message to the OpenID federation entity configuration endpoint.
+ */
+public class EntityConfigurationResponse extends AbstractSignedJWTResponse {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("entity-statement+jwt");
+
+ /** The content type. */
+ @Nonnull public static final ContentType HTTP_RESPONSE_CONTENT_TYPE =
+ new ContentType("application", JWT_TYPE_HEADER.toString());
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param statement entity statement
+ */
+ public EntityConfigurationResponse(@Nonnull final SignedJWT statement) {
+ super(statement);
+ }
+
+ /** {@inheritDoc} */
+ protected ContentType getHttpResponseContentType() {
+ return HTTP_RESPONSE_CONTENT_TYPE;
+ }
+
+ /** {@inheritDoc} */
+ protected JOSEObjectType getJWTTypeHeader() {
+ return JWT_TYPE_HEADER;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ExplicitClientRegistrationRequest.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ExplicitClientRegistrationRequest.java
new file mode 100644
index 0000000..59a6148
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ExplicitClientRegistrationRequest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.oidfed.messaging.impl;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.nimbusds.oauth2.sdk.Request;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Request message to the OpenID federation explicit registration endpoint.
+ */
+public class ExplicitClientRegistrationRequest implements Request {
+
+ /** The endpoint URI of the request. */
+ @Nonnull private final URI endpointUri;
+
+ /** The entity configuration from the request. */
+ @Nullable private final EntityConfiguration entityConfiguration;
+
+ /** The trust chain from the request. */
+ @Nullable private final List<EntityStatement<?>> trustChain;
+
+ /**
+ * Constructor.
+ *
+ * @param uri endpoint URI
+ * @param configuration client configuration
+ */
+ public ExplicitClientRegistrationRequest(@Nonnull final URI uri,
+ @Nonnull final EntityConfiguration configuration) {
+ endpointUri = Constraint.isNotNull(uri, "Endpoint URI cannot be null");
+ entityConfiguration = Constraint.isNotNull(configuration, "Entity configuration cannot be null");
+ trustChain = null;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param uri endpoint URI
+ * @param chain trust chain
+ */
+ public ExplicitClientRegistrationRequest(@Nonnull final URI uri,
+ @Nonnull @NotEmpty final List<EntityStatement<?>> chain) {
+ endpointUri = Constraint.isNotNull(uri, "Endpoint URI cannot be null");
+ Constraint.isNotEmpty(chain, "Trust chain cannot be empty");
+ trustChain = chain;
+ entityConfiguration = null;
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public URI getEndpointURI() {
+ return endpointUri;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public HTTPRequest toHTTPRequest() {
+ //TODO
+ return null;
+ }
+
+ /**
+ * Get the entity configuration from the request.
+ *
+ * @return entity configuration
+ */
+ @Nullable public EntityConfiguration getEntityConfiguration() {
+ return entityConfiguration;
+ }
+
+ /**
+ * Get the trust chain from the request.
+ *
+ * @return trust chain
+ */
+ @Nullable public List<EntityStatement<?>> getTrustChain() {
+ return trustChain;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ExplicitClientRegistrationResponse.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ExplicitClientRegistrationResponse.java
new file mode 100644
index 0000000..8ec3cf3
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ExplicitClientRegistrationResponse.java
@@ -0,0 +1,88 @@
+/*
+ * 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.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 explicit registration endpoint.
+ */
+public class ExplicitClientRegistrationResponse extends AbstractSignedJWTResponse {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("explicit-registration-response+jwt");
+
+ /** The content type. */
+ @Nonnull public static final ContentType HTTP_RESPONSE_CONTENT_TYPE =
+ new ContentType("application", JWT_TYPE_HEADER.toString());
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param statement entity statement
+ */
+ public ExplicitClientRegistrationResponse(@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 explicit registration success response from the given HTTP response.
+ *
+ * @param httpResponse the HTTP response
+ * @return explicit registration success response
+ * @throws ParseException if HTTP response could not be parsed into registration response
+ */
+ @Nonnull
+ public static ExplicitClientRegistrationResponse 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 ExplicitClientRegistrationResponse(jwt);
+ } catch (final java.text.ParseException | ConstraintViolationException e) {
+ throw new ParseException(e.getMessage(), e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ResolveEntityRequest.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ResolveEntityRequest.java
new file mode 100644
index 0000000..257c65d
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ResolveEntityRequest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.oidfed.messaging.impl;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.base.MoreObjects;
+import com.nimbusds.oauth2.sdk.AbstractOptionallyAuthenticatedRequest;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Request message to the OpenID federation resolve entity API.
+ */
+public class ResolveEntityRequest extends AbstractOptionallyAuthenticatedRequest {
+
+ /** The requested subject. */
+ @Nonnull @NotEmpty private final String subject;
+
+ /** The requested trust anchors. */
+ @Nonnull @NotEmpty private final List<String> trustAnchors;
+
+ /** The requested entity types to resolve. */
+ @Nonnull private final List<String> entityTypes;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param uri endpoint URI
+ * @param sub subject
+ * @param anchors trust anchors
+ * @param types optional entity types
+ * @param clientAuthentication optional client authentication
+ */
+ public ResolveEntityRequest(@Nonnull final URI uri,
+ @Nonnull @NotEmpty final String sub,
+ @Nonnull @NotEmpty final List<String> anchors,
+ @Nullable final List<String> types,
+ @Nullable final ClientAuthentication clientAuthentication) {
+ super(Constraint.isNotNull(uri, "Endpoint URI cannot be null"), clientAuthentication);
+ subject = Constraint.isNotNull(sub, "Subject cannot be empty");
+ Constraint.isNotEmpty(anchors, "Trust anchors cannot be empty");
+ trustAnchors = anchors;
+ entityTypes = types == null ? CollectionSupport.emptyList() : CollectionSupport.copyToList(types);
+ }
+
+ /**
+ * Returns the requested subject.
+ *
+ * @return The subject.
+ */
+ @Nonnull @NotEmpty public String getSubject() {
+ return subject;
+ }
+
+ /**
+ * Returns the requested trust anchor.
+ *
+ * @return The trust anchor.
+ */
+ @Nonnull @NotEmpty public List<String> getTrustAnchors() {
+ return trustAnchors;
+ }
+
+ /**
+ * Returns the requested entity type to resolve.
+ *
+ * @return The entity type to resolve.
+ */
+ @Nonnull public List<String> getEntityTypes() {
+ return entityTypes;
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull public URI getEndpointURI() {
+ final URI result = super.getEndpointURI();
+ assert result != null;
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public HTTPRequest toHTTPRequest() {
+ //TODO
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("subject", getSubject())
+ .add("trustAnchors", getTrustAnchors())
+ .add("entityTypes", getEntityTypes())
+ .add("endpointURI", getEndpointURI())
+ .add("clientAuthentication", getClientAuthentication())
+ .toString();
+ }
+
+ /** {@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 ResolveEntityRequest other = (ResolveEntityRequest) obj;
+ return getEndpointURI().equals(other.getEndpointURI()) && subject.equals(other.subject) &&
+ entityTypes.containsAll(other.entityTypes) && other.entityTypes.containsAll(entityTypes) &&
+ trustAnchors.containsAll(other.trustAnchors) && other.trustAnchors.containsAll(trustAnchors) &&
+ getClientAuthentication() == null ? other.getClientAuthentication() == null :
+ getClientAuthentication().equals(other.getClientAuthentication());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ResolveEntityResponse.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ResolveEntityResponse.java
new file mode 100644
index 0000000..ea0dbee
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/ResolveEntityResponse.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.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 resolve entity endpoint.
+ */
+public class ResolveEntityResponse extends AbstractSignedJWTResponse {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("resolve-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 ResolveEntityResponse(@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 ResolveEntityResponse 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 ResolveEntityResponse(jwt);
+ } catch (final java.text.ParseException | ConstraintViolationException e) {
+ throw new ParseException(e.getMessage(), e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/SignedKeysetResponse.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/SignedKeysetResponse.java
new file mode 100644
index 0000000..c599c27
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/messaging/impl/SignedKeysetResponse.java
@@ -0,0 +1,55 @@
+/*
+ * 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.oidfed.messaging.impl;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.common.contenttype.ContentType;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+/**
+ * Response message to the OpenID federation signed keyset endpoint.
+ */
+public class SignedKeysetResponse extends AbstractSignedJWTResponse {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("jwk-set+jwt");
+
+ /** The content type. */
+ @Nonnull public static final ContentType HTTP_RESPONSE_CONTENT_TYPE =
+ new ContentType("application", JWT_TYPE_HEADER.toString());
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param statement entity statement
+ */
+ public SignedKeysetResponse(@Nonnull final SignedJWT statement) {
+ super(statement);
+ }
+
+ /** {@inheritDoc} */
+ protected ContentType getHttpResponseContentType() {
+ return HTTP_RESPONSE_CONTENT_TYPE;
+ }
+
+ /** {@inheritDoc} */
+ protected JOSEObjectType getJWTTypeHeader() {
+ return JWT_TYPE_HEADER;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractFederationEndpointResponseFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractFederationEndpointResponseFetchingStrategy.java
new file mode 100644
index 0000000..7599059
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractFederationEndpointResponseFetchingStrategy.java
@@ -0,0 +1,328 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+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.HttpRequest;
+import org.apache.hc.core5.http.NameValuePair;
+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 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> data needed for request
+ * @param <C> metadata container class stored to the metadata cache
+ */
+public abstract class
+ AbstractFederationEndpointResponseFetchingStrategy<R extends Object, C extends BaseExpirableMetadataContainer>
+ 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 protected ObjectMapper objectMapper;
+
+ /** Strategy to fetch request data from the criteria set. */
+ @NonnullAfterInit private Function<CriteriaSet, R> criteriaToRequestDataStrategy;
+
+ /** Strategy to fetch lifetime for container with valid contents. */
+ @NonnullAfterInit private Function<CriteriaSet, Duration> criteriaToValidContainerLifetimeStrategy;
+
+ /** Strategy to fetch lifetime for container with invalid contents. */
+ @NonnullAfterInit private Function<CriteriaSet, Duration> criteriaToInvalidContainerLifetimeStrategy;
+
+ /** Strategy to fetch lifetime for containers whose contents could not be fetched. */
+ @NonnullAfterInit private Function<CriteriaSet, Duration> criteriaToExceptionContainerLifetimeStrategy;
+
+ /** Strategy to fetch client authentication decorator for HTTP request and its parameters. */
+ @Nullable protected
+ Function<CriteriaSet, BiConsumer<HttpRequest,List<NameValuePair>>> clientAuthenticationLookupStrategy;
+
+ /**
+ * 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 data from the criteria set.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setCriteriaToRequestDataStrategy(@Nonnull final Function<CriteriaSet, R> strategy) {
+ checkSetterPreconditions();
+ criteriaToRequestDataStrategy =
+ Constraint.isNotNull(strategy, "Criteria to request data 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");
+ }
+
+ /**
+ * Set the strategy to fetch lifetime for container with valid contents.
+ *
+ * @param strategy lifetime strategy
+ */
+ public void setCriteriaToValidContainerLifetimeStrategy(@Nonnull final Function<CriteriaSet, Duration> strategy) {
+ checkSetterPreconditions();
+
+ criteriaToValidContainerLifetimeStrategy =
+ Constraint.isNotNull(strategy, "Criteria to valid container lifetime strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy to fetch lifetime for container with invalid contents.
+ *
+ * @param strategy lifetime strategy
+ */
+ public void setCriteriaToInvalidContainerLifetimeStrategy(
+ @Nonnull final Function<CriteriaSet, Duration> strategy) {
+ checkSetterPreconditions();
+
+ criteriaToInvalidContainerLifetimeStrategy =
+ Constraint.isNotNull(strategy, "Criteria to invalid container lifetime strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy to fetch lifetime for container whose contents could not be fetched.
+ *
+ * @param strategy lifetime strategy
+ */
+ public void setCriteriaToExceptionContainerLifetimeStrategy(
+ @Nonnull final Function<CriteriaSet, Duration> strategy) {
+ checkSetterPreconditions();
+
+ criteriaToExceptionContainerLifetimeStrategy =
+ Constraint.isNotNull(strategy, "Criteria to exception container lifetime strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy to fetch client authentication decorator for HTTP request and its parameters.
+ *
+ * @param strategy client authentication strategy
+ */
+ public void setClientAuthenticationLookupStrategy(
+ @Nullable final Function<CriteriaSet, BiConsumer<HttpRequest, List<NameValuePair>>> strategy) {
+ checkSetterPreconditions();
+
+ clientAuthenticationLookupStrategy = strategy;
+ }
+
+ /** {@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 (criteriaToRequestDataStrategy == null) {
+ throw new ComponentInitializationException("Criteria to request data strategy cannot be null");
+ }
+ if (criteriaToValidContainerLifetimeStrategy == null) {
+ throw new ComponentInitializationException("Criteria to valid container lifetime strategy cannot be null");
+ }
+ if (criteriaToInvalidContainerLifetimeStrategy == null) {
+ throw new ComponentInitializationException(
+ "Criteria to invalid container lifetime strategy cannot be null");
+ }
+ if (criteriaToExceptionContainerLifetimeStrategy == null) {
+ throw new ComponentInitializationException(
+ "Criteria to exception container lifetime 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 requestData = criteriaToRequestDataStrategy.apply(criteria);
+ if (requestData == null) {
+ log.debug("Could not resolve request data from criteria {}", criteria);
+ return null;
+ }
+ final Duration validLifetime = criteriaToValidContainerLifetimeStrategy.apply(criteria);
+ if (validLifetime == null) {
+ log.warn("Could not fetch expiration time for valid container");
+ return null;
+ }
+ final Instant validExpiration = Instant.now().plus(validLifetime);
+ final Duration invalidLifetime = criteriaToInvalidContainerLifetimeStrategy.apply(criteria);
+ if (invalidLifetime == null) {
+ log.warn("Could not fetch expiration time for invalid container");
+ return null;
+ }
+ final Instant invalidExpiration = Instant.now().plus(invalidLifetime);
+ final Duration exceptionLifetime = criteriaToExceptionContainerLifetimeStrategy.apply(criteria);
+ if (exceptionLifetime == null) {
+ log.warn("Could not fetch expiration time for exception container");
+ return null;
+ }
+ final Instant exceptionExpiration = Instant.now().plus(exceptionLifetime);
+ final ClassicHttpRequest httpRequest = initializeHttpRequest(criteria, requestData);
+ if (httpRequest == null) {
+ log.debug("Could not initialize HTTP request for {}", requestData);
+ return null;
+ }
+ final HttpClientContext httpContext = buildHttpContext(httpRequest);
+ try (final ClassicHttpResponse response = httpClient.executeOpen(null, httpRequest, httpContext)) {
+ final String scheme = httpRequest.getUri().getScheme();
+ if (scheme == null) {
+ throw new URISyntaxException(httpRequest.getUri().toString(), "Could not parse scheme");
+ }
+ HttpClientSecuritySupport.checkTLSCredentialEvaluated(httpContext, scheme);
+ assert validExpiration != null; assert invalidExpiration != null; assert exceptionExpiration != null;
+ return parseHttpResponse(criteria, requestData, response, validExpiration, invalidExpiration,
+ exceptionExpiration);
+ } catch (final ProtocolException | URISyntaxException | IOException e) {
+ log.debug("Unable to fetch resolve entity response via request data: {}", requestData, e);
+ assert exceptionExpiration != null;
+ return handleException(criteria, requestData, e, exceptionExpiration);
+ }
+ }
+
+ /**
+ * Get the client authentication decorator by using the configured lookup strategy.
+ *
+ * @param criteria the criteria passed to the lookup strategy
+ * @return the client authentication decorator, or null if none was resolved
+ */
+ @Nullable protected BiConsumer<HttpRequest, List<NameValuePair>> getClientAuthentication(
+ @Nonnull final CriteriaSet criteria) {
+ log.trace("Criteria set passed for client authentication lookup: {}", criteria);
+ return Optional.ofNullable(clientAuthenticationLookupStrategy)
+ .map(strategy -> strategy.apply(criteria))
+ .orElse(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 requestData request data
+ * @return the request to be used with {@link #httpClient}
+ */
+ @Nullable protected abstract ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+ @Nonnull final R requestData);
+
+ /**
+ * Parses the response message container to be stored in the metadata cache.
+ *
+ * @param criteria criteria set
+ * @param requestData the request data
+ * @param response the HTTP response obtained from {@link #httpClient}
+ * @param validExpiration expiration instant for containers with valid content
+ * @param invalidExpiration expiration instant for container with invalid content
+ * @param exceptionExpiration expiration instant for container whose contents could not be fetched
+ * @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 requestData, @Nullable final ClassicHttpResponse response,
+ @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+ @Nonnull final Instant exceptionExpiration) throws ProtocolException, IOException;
+
+ /**
+ * Handles the exception catched while communicating with the remote API.
+ *
+ * @param criteria criteria set
+ * @param requestData the request data
+ * @param throwable exception catched while communicating with the remote API
+ * @param expiration exoiration instant for container whose contents could not be fetched
+ * @return the container to be stored in the metadata cache
+ */
+ @Nullable protected abstract C handleException(@Nonnull final CriteriaSet criteria,
+ @Nonnull final R requestData, @Nonnull final Throwable throwable, @Nonnull final Instant expiration);
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractTrustEngineSignatureValidationComponent.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractTrustEngineSignatureValidationComponent.java
new file mode 100644
index 0000000..dc4ee67
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractTrustEngineSignatureValidationComponent.java
@@ -0,0 +1,103 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.text.ParseException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.trust.TrustEngine;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import 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;
+
+/**
+ * Abstract component performing signature validation via {@link TrustEngine}.
+ */
+public class AbstractTrustEngineSignatureValidationComponent extends AbstractIdentifiableInitializableComponent {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(AbstractTrustEngineSignatureValidationComponent.class);
+
+ /** Trust engine used to validate a signature. */
+ @NonnullAfterInit private TrustEngine<SignedJWT> trustEngine;
+
+ /** Set of externally specified default criteria for input to the trust engine. */
+ @Nullable private CriteriaSet defaultCriteria;
+
+ /**
+ * Set trust engine used to validate a signature.
+ *
+ * @param engine trust engine
+ */
+ public void setTrustEngine(@Nonnull final TrustEngine<SignedJWT> engine) {
+ checkSetterPreconditions();
+ trustEngine = Constraint.isNotNull(engine, "Trust Engine cannot be null");
+ }
+
+ /**
+ * Set the optional set of default criteria used as input to the trust engine.
+ *
+ * @param criteria criteria set to use
+ */
+ public void setDefaultCriteria(@Nullable final CriteriaSet criteria) {
+ checkSetterPreconditions();
+ defaultCriteria = criteria;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (trustEngine == null) {
+ throw new ComponentInitializationException("Trust Engine cannot be null");
+ }
+ }
+
+ /**
+ * Validates the given JWT via trust engine and the given criteria.
+ *
+ * @param jwt the JWT to be validated
+ * @param criteria the criteria (expanded with the optional default criteria)
+ * @param entityId the entity ID used for logging
+ * @return true if validation was successful, false otherwise
+ */
+ protected boolean validateJwt(@Nonnull final SignedJWT jwt, @Nonnull final CriteriaSet criteria,
+ @Nullable final String entityId) {
+ if (defaultCriteria != null && !defaultCriteria.isEmpty()) {
+ criteria.addAll(defaultCriteria);
+ }
+ try {
+ if (trustEngine.validate(jwt, criteria)) {
+ log.debug("Successfully validated entity statement for {}", entityId);
+ return true;
+ }
+ log.warn("Trust Engine validation failed for {}, issued by {}", entityId,
+ jwt.getJWTClaimsSet().getIssuer());
+ } catch (final SecurityException | ParseException e) {
+ log.warn("Could not validate entity statement for {}", entityId, e);
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/BaseExpirableMetadataContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/BaseExpirableMetadataContainer.java
new file mode 100644
index 0000000..009b198
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/BaseExpirableMetadataContainer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A base container class for metadata caches carrying expiration instant.
+ */
+public abstract class BaseExpirableMetadataContainer implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 656269369356865370L;
+
+ /** Expiration instant for this container. */
+ @Nonnull private final Instant expiration;
+
+ /**
+ * Constructor.
+ *
+ * @param expirationInstant expiration instant, must not be null
+ */
+ public BaseExpirableMetadataContainer(@Nonnull final Instant expirationInstant) {
+ expiration = Constraint.isNotNull(expirationInstant, "Expiration instant cannot be null");
+ }
+
+ /**
+ * Get expiration instant.
+ *
+ * @return expiration instant
+ */
+ @Nonnull public Instant getExpirationInstant() {
+ return expiration;
+ }
+
+ /**
+ * Get the entity ID of the requested entity.
+ *
+ * @return requested entity ID
+ */
+ @Nonnull @NotEmpty public abstract String getEntityId();
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/BaseExpirableStatementContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/BaseExpirableStatementContainer.java
new file mode 100644
index 0000000..1f03c46
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/BaseExpirableStatementContainer.java
@@ -0,0 +1,97 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.BaseJWTWrapper;
+import net.shibboleth.oidfed.metadata.BasePayload;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A base container class for metadata caches carrying expiration instant and statement.
+ *
+ * @param <T> wrapped statement
+ */
+public abstract class BaseExpirableStatementContainer<T extends BaseJWTWrapper<? extends BasePayload>>
+ extends BaseExpirableMetadataContainer
+ implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 656269369356432370L;
+
+ /** Entity statement for this container. */
+ @Nullable private final T statement;
+
+ /** Flag to indicate if {@link #statement} is valid. */
+ private boolean validStatement;
+
+ /** Expiration instant for this container when invalid statement is contained. */
+ @Nonnull private final Instant invalidStatementExpiration;
+
+ /**
+ * Constructor.
+ *
+ * @param validExpirationInstant expiration instant for valid statement, must not be null
+ * @param entityStatement entity statement
+ * @param invalidExpirationInstant expiration instant for invalid statement, must not be null
+ */
+ public BaseExpirableStatementContainer(@Nonnull final Instant validExpirationInstant,
+ @Nullable final T entityStatement, @Nonnull final Instant invalidExpirationInstant) {
+ super(validExpirationInstant);
+ statement = entityStatement;
+ validStatement = entityStatement != null;
+ invalidStatementExpiration = Constraint.isNotNull(invalidExpirationInstant,
+ "Expiration instant for invalid statements cannot be null");
+ }
+
+ /**
+ * Get the statement. If the statement has been invalidated, a null is returned.
+ *
+ * @return entity statement
+ */
+ @Nullable public T getStatement() {
+ return getStatement(false);
+ }
+
+ /**
+ * Get the statement.
+ *
+ * @param includeInvalidStatement a flag to return invalidated entity statement
+ * @return entity statement
+ */
+ @Nullable public T getStatement(final boolean includeInvalidStatement) {
+ return includeInvalidStatement ? statement : validStatement ? statement : null;
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull public Instant getExpirationInstant() {
+ return getStatement() != null ? super.getExpirationInstant() : invalidStatementExpiration;
+ }
+
+ /**
+ * Invalidates the entity statement.
+ *
+ * @return this container
+ */
+ public BaseExpirableStatementContainer<T> invalidateStatement() {
+ validStatement = false;
+ return this;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultAuthenticationSecurityConfigurationLookupFunction.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultAuthenticationSecurityConfigurationLookupFunction.java
new file mode 100644
index 0000000..df5f7ab
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultAuthenticationSecurityConfigurationLookupFunction.java
@@ -0,0 +1,117 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.profile.config.JSONSecurityConfiguration;
+import net.shibboleth.oidfed.profile.config.OIDFederationEntityConfigurationProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
+import net.shibboleth.profile.relyingparty.RelyingPartyConfigurationResolver;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+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;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.shared.service.ServiceableComponent;
+
+/**
+ * Default lookup function for resolving the security configuration via {@link RelyingPartyConfigurationResolver} and
+ * a configurable unverified profile configuration. The {@link OIDFederationEntityConfigurationProfileConfiguration}
+ * profile is used by default.
+ */
+public class DefaultAuthenticationSecurityConfigurationLookupFunction extends AbstractIdentifiableInitializableComponent
+ implements Function<CriteriaSet, JSONSecurityConfiguration> {
+
+ /** Class logger. */
+ @Nonnull
+ private Logger log = LoggerFactory.getLogger(DefaultAuthenticationSecurityConfigurationLookupFunction.class);
+
+ /** Resolver used to look up relying party configurations. */
+ @NonnullAfterInit private ReloadableService<RelyingPartyConfigurationResolver> rpConfigResolver;
+
+ /** Profile configuration identifier, must be unverified relying party. */
+ @Nonnull @NotEmpty private String profileId;
+
+ /**
+ * Constructor.
+ */
+ public DefaultAuthenticationSecurityConfigurationLookupFunction() {
+ profileId = OIDFederationEntityConfigurationProfileConfiguration.PROFILE_ID;
+ }
+
+ /**
+ * Set the relying party config resolver to use.
+ *
+ * @param resolver the resolver to use
+ */
+ public void setRelyingPartyConfigurationResolver(
+ @Nonnull final ReloadableService<RelyingPartyConfigurationResolver> resolver) {
+ checkSetterPreconditions();
+ rpConfigResolver = Constraint.isNotNull(resolver, "Relying party configuration resolver cannot be null");
+ }
+
+ /**
+ * Set the unverified profile configuration identifier.
+ *
+ * @param id the identifier to use
+ */
+ public void setUnverifiedProfileConfigurationId(@Nonnull @NotEmpty final String id) {
+ checkSetterPreconditions();
+ profileId = Constraint.isNotEmpty(id, "Unverified profile configuration ID cannot be empty");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (rpConfigResolver == null) {
+ throw new ComponentInitializationException("RelyingPartyConfigurationResolver cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public JSONSecurityConfiguration apply(@Nullable final CriteriaSet criteria) {
+ checkComponentActive();
+ try (final ServiceableComponent<RelyingPartyConfigurationResolver> resolver =
+ rpConfigResolver.getServiceableComponent()) {
+ final RelyingPartyConfiguration config = resolver.getComponent().resolveSingle(criteria);
+ if (config != null) {
+ final ProfileConfiguration profileConfig = config.getProfileConfiguration(null, profileId);
+ if (profileConfig != null && profileConfig.getSecurityConfiguration(null)
+ instanceof JSONSecurityConfiguration jsonSecurityConfig) {
+ return jsonSecurityConfig;
+ }
+ log.error("Could not resolve JSON security configuration via profile configuration {}", profileId);
+ }
+ log.error("Could not resolve relying party configuration");
+ } catch (final ResolverException e) {
+ log.error("Error trying to resolve relying party configuration", e);
+ }
+ return null;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultClientAuthenticationLookupFunction.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultClientAuthenticationLookupFunction.java
new file mode 100644
index 0000000..7ae82e8
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultClientAuthenticationLookupFunction.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.oidfed.metadata.cache;
+
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.NameValuePair;
+import org.slf4j.Logger;
+
+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;
+
+/**
+ * Default client authentication lookup strategy implementation that exploits a static list of supported authentication
+ * functions.
+ */
+public class DefaultClientAuthenticationLookupFunction extends AbstractIdentifiableInitializableComponent
+ implements Function<CriteriaSet,BiConsumer<HttpRequest,List<NameValuePair>>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultClientAuthenticationLookupFunction.class);
+
+ /** Lookup strategy for supported authentication methods. */
+ @NonnullAfterInit private Function<CriteriaSet,List<String>> supportedAuthenticationMethodsLookupStrategy;
+
+ /** Lookup strategy for the list of supported client authentication decorators. */
+ @NonnullAfterInit
+ private List<BiFunction<CriteriaSet,List<String>,BiConsumer<HttpRequest,List<NameValuePair>>>>
+ clientAuthenticationDecoratorsLookupStrategy;
+
+ /**
+ * Set the lookup strategy for supported authentication methods.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSupportedAuthenticationMethodsLookupStrategy(
+ @Nonnull final Function<CriteriaSet,List<String>> strategy) {
+ checkSetterPreconditions();
+ supportedAuthenticationMethodsLookupStrategy =
+ Constraint.isNotNull(strategy, "Supported authentication methods lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup strategy for the list of supported client authentication decorators.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setClientAuthenticationDecoratorsLookupStrategy(@Nonnull final
+ List<BiFunction<CriteriaSet,List<String>,BiConsumer<HttpRequest,List<NameValuePair>>>> strategy) {
+ checkSetterPreconditions();
+ clientAuthenticationDecoratorsLookupStrategy =
+ Constraint.isNotNull(strategy, "Client authentication decorators lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (supportedAuthenticationMethodsLookupStrategy == null) {
+ throw new ComponentInitializationException(
+ "Supported authentication methods lookup strategy cannot be null");
+ }
+ if (clientAuthenticationDecoratorsLookupStrategy == null) {
+ throw new ComponentInitializationException(
+ "Client authentication decorators lookup strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public BiConsumer<HttpRequest,List<NameValuePair>> apply(@Nullable final CriteriaSet criteria) {
+ final List<String> supportedMethods = supportedAuthenticationMethodsLookupStrategy.apply(criteria);
+ if (supportedMethods == null) {
+ log.debug("Could not resolve list of supported methods, no client authentication used");
+ return null;
+ }
+ if (supportedMethods.isEmpty()) {
+ log.warn("Unexpected value for the list of supported methods: empty array");
+ return null;
+ }
+ for (final BiFunction<CriteriaSet,List<String>,BiConsumer<HttpRequest,List<NameValuePair>>> function :
+ clientAuthenticationDecoratorsLookupStrategy) {
+ final BiConsumer<HttpRequest,List<NameValuePair>> consumer = function.apply(criteria, supportedMethods);
+ if (consumer != null) {
+ log.debug("Returning client authentication decorator {}", consumer);
+ return consumer;
+ }
+ }
+ log.debug("None of the supported methods returned any client authentication details");
+ return null;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java
new file mode 100644
index 0000000..cb54724
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java
@@ -0,0 +1,181 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.text.ParseException;
+import java.util.Optional;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
+import net.shibboleth.oidfed.metadata.BaseJWTWrapper;
+import net.shibboleth.oidfed.metadata.BasePayload;
+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.primitive.NonnullSupplier;
+
+/**
+ * Default content validating filter for entity statement. Verifies that the
+ * {@link BaseExpirableStatementContainer#getEntityId()} matches with the subject of the entity statement and
+ * validates the claims via configurable claims validator. The {@link ProfileRequestContext} that is fed to the claims
+ * validator is fetched from the {@link HttpServletRequest} attributes. An empty context is fed if the attribute was
+ * null.
+ */
+ at ThreadSafeAfterInit
+public class DefaultEntityStatementContentValidationCondition
+ extends AbstractIdentifiableInitializableComponent
+ implements BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext> {
+
+ /** Class logger. */
+ @Nonnull private Logger log =
+ LoggerFactory.getLogger(DefaultEntityStatementContentValidationCondition.class);
+
+ /** The claims validator to use for validating the entity statement claims. */
+ @NonnullAfterInit private ClaimsValidator claimsValidator;
+
+ /** The header validator to use for validating the entity statement header. */
+ @NonnullAfterInit private BiPredicate<SignedJWT, ProfileRequestContext> headerValidator;
+
+ /** Supplier for the Current HTTP request, if available. */
+ @NonnullAfterInit private NonnullSupplier<HttpServletRequest> httpServletRequestSupplier;
+
+ @NonnullAfterInit
+ private BiPredicate<BaseExpirableStatementContainer<?>, BaseJWTWrapper<? extends BasePayload>>
+ containerIdValidationStrategy;
+
+ /**
+ * Set the claims validator to use for validating the entity statement claims.
+ *
+ * @param validator claims validator
+ */
+ public void setClaimsValidator(@Nonnull final ClaimsValidator validator) {
+ checkSetterPreconditions();
+ claimsValidator = Constraint.isNotNull(validator, "Claims validator cannot be null");
+ }
+
+ /**
+ * Set the header validator to use for validating the entity statement header.
+ *
+ * @param validator header validator
+ */
+ public void setHeaderValidator(@Nonnull final BiPredicate<SignedJWT, ProfileRequestContext> validator) {
+ checkSetterPreconditions();
+ headerValidator = Constraint.isNotNull(validator, "Header validator cannot be null");
+ }
+
+ /**
+ * Set the current HTTP request Supplier.
+ *
+ * @param requestSupplier Supplier for the current HTTP request
+ */
+ public void setHttpServletRequestSupplier(@Nonnull final NonnullSupplier<HttpServletRequest> requestSupplier) {
+ checkSetterPreconditions();
+ httpServletRequestSupplier = Constraint.isNotNull(requestSupplier,
+ "Http servlet request supplier cannot be null");
+ }
+
+ /**
+ * Set the validation strategy for the container identifier.
+ *
+ * @param strategy validation strategy
+ */
+ public void setContainerIdValidationStrategy(@Nonnull final
+ BiPredicate<BaseExpirableStatementContainer<?>, BaseJWTWrapper<? extends BasePayload>> strategy) {
+ checkSetterPreconditions();
+ containerIdValidationStrategy = Constraint.isNotNull(strategy,
+ "Container ID validation strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (claimsValidator == null) {
+ throw new ComponentInitializationException("Claims validator cannot be null");
+ }
+ if (headerValidator == null) {
+ throw new ComponentInitializationException("Header validator cannot be null");
+ }
+ if (httpServletRequestSupplier == null) {
+ throw new ComponentInitializationException("Http servlet request supplier cannot be null");
+ }
+ if (containerIdValidationStrategy == null) {
+ throw new ComponentInitializationException("Container ID validation strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final BaseExpirableStatementContainer<?> responseContainer,
+ @Nullable final MetadataFilterContext filterContext) {
+ checkComponentActive();
+
+ if (responseContainer == null || responseContainer.getStatement() == null) {
+ return false;
+ }
+
+ final var wrapper = responseContainer.getStatement();
+ assert wrapper != null;
+ if (!containerIdValidationStrategy.test(responseContainer, wrapper)) {
+ log.warn("Container ID validation failed with the requested ID {}", responseContainer.getEntityId());
+ return false;
+ }
+ final ProfileRequestContext profileRequestContext =
+ Optional.ofNullable(httpServletRequestSupplier.get().getAttribute(ProfileRequestContext.BINDING_KEY))
+ .filter(ProfileRequestContext.class::isInstance)
+ .map(ProfileRequestContext.class::cast)
+ .orElse(null);
+ final ProfileRequestContext nonNullProfileRequestContext;
+ if (profileRequestContext == null) {
+ log.warn("Could not resolve profile request context, supplying an empty one to claims validators");
+ nonNullProfileRequestContext = new ProfileRequestContext();
+ } else {
+ nonNullProfileRequestContext = profileRequestContext;
+ }
+
+ //TODO: make validator interface to commons?
+ if (!headerValidator.test(wrapper.getJwt(), nonNullProfileRequestContext)) {
+ log.warn("Header validation failed for entity statement {}", responseContainer.getEntityId());
+ return false;
+ }
+
+ try {
+ final JWTClaimsSet claimsSet = wrapper.getJwt().getJWTClaimsSet();
+ assert claimsSet != null;
+ claimsValidator.validate(claimsSet,
+ profileRequestContext != null ? profileRequestContext : new ProfileRequestContext());
+ } catch (final JWTValidationException | ParseException e) {
+ log.warn("Claims validation failed for entity statement {}", responseContainer.getEntityId(), e);
+ return false;
+ }
+ log.trace("Entity statement claims successfully validated for {}", responseContainer.getEntityId());
+ return true;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementMetadataFilterStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementMetadataFilterStrategy.java
new file mode 100644
index 0000000..1fb5dc3
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementMetadataFilterStrategy.java
@@ -0,0 +1,120 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default metadata validation filter for entity statement containers.
+ */
+public class DefaultEntityStatementMetadataFilterStrategy
+ extends AbstractIdentifiableInitializableComponent implements
+ BiFunction<BaseExpirableStatementContainer<?>, MetadataFilterContext, BaseExpirableStatementContainer<?>> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultEntityStatementMetadataFilterStrategy.class);
+
+ /** List of validation conditions. */
+ @NonnullAfterInit
+ private List<BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext>> validationConditions;
+
+ /** List of custom metadata filter strategies. */
+ @Nonnull private List<BiFunction<BaseExpirableStatementContainer<?>,
+ MetadataFilterContext, BaseExpirableStatementContainer<?>>> customFilterStrategies;
+
+ /**
+ * Constructor.
+ */
+ public DefaultEntityStatementMetadataFilterStrategy() {
+ customFilterStrategies = CollectionSupport.emptyList();
+ }
+
+ /**
+ * Set the list of validation conditions.
+ *
+ * @param predicates validation conditions
+ */
+ public void setValidationConditions(
+ @Nonnull final List<BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext>> predicates) {
+ checkSetterPreconditions();
+ validationConditions = Constraint.isNotNull(predicates, "List of validation conditions cannot be null");
+ }
+
+ /**
+ * Set the list of custom metadata filter strategies.
+ *
+ * @param strategies metadata filter strategies
+ */
+ public void setCustomFilterStrategies(@Nullable final List<BiFunction<BaseExpirableStatementContainer<?>,
+ MetadataFilterContext, BaseExpirableStatementContainer<?>>> strategies) {
+ checkSetterPreconditions();
+ customFilterStrategies = strategies == null ? CollectionSupport.emptyList() : strategies;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (validationConditions == null) {
+ throw new ComponentInitializationException("List of validation conditions cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public BaseExpirableStatementContainer<?> apply(@Nullable final BaseExpirableStatementContainer<?> container,
+ @Nullable final MetadataFilterContext filterContext) {
+ checkComponentActive();
+ if (container == null) {
+ return null;
+ }
+ for (final BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext> condition :
+ validationConditions) {
+ if (!condition.test(container, filterContext)) {
+ log.debug("Condition {} failed, invalidating container for {}", condition, container.getEntityId());
+ return container.invalidateStatement();
+ }
+ }
+ log.debug("Metadata container successfully validated by all predicates for {}", container.getEntityId());
+
+ BaseExpirableStatementContainer<?> result = container;
+ for (final BiFunction<BaseExpirableStatementContainer<?>, MetadataFilterContext,
+ BaseExpirableStatementContainer<?>> customFilterStrategy : customFilterStrategies) {
+ log.trace("Applying custom filter strategy {}", customFilterStrategy);
+ result = customFilterStrategy.apply(result, filterContext);
+ if (result == null) {
+ log.debug("Custom filter strategy {} returned null", customFilterStrategy);
+ return null;
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementSignatureValidationCondition.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementSignatureValidationCondition.java
new file mode 100644
index 0000000..cb19c85
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementSignatureValidationCondition.java
@@ -0,0 +1,96 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.trust.TrustEngine;
+import org.slf4j.Logger;
+
+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 entity statement. The signature validation is performed via configurable
+ * {@link TrustEngine}. The {@link CriteriaSet} passed to trust engine is fetched via configurable strategy.
+ */
+ at ThreadSafeAfterInit
+public class DefaultEntityStatementSignatureValidationCondition
+ extends AbstractTrustEngineSignatureValidationComponent
+ implements BiPredicate<BaseExpirableStatementContainer<?>, MetadataFilterContext> {
+
+ /** Class logger. */
+ @Nonnull private Logger log =
+ LoggerFactory.getLogger(DefaultEntityStatementSignatureValidationCondition.class);
+
+ /** Lookup strategy for criteria set passed to the trust engine. */
+ @NonnullAfterInit private Function<BaseExpirableStatementContainer<?>, CriteriaSet> criteriaSetLookupStrategy;
+
+ /**
+ * Set the lookup strategy for criteria set passed to the trust engine.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setCriteriaSetLookupStrategy(
+ @Nonnull final Function<BaseExpirableStatementContainer<?>, CriteriaSet> strategy) {
+ checkSetterPreconditions();
+ criteriaSetLookupStrategy = Constraint.isNotNull(strategy, "CriteriaSet lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (criteriaSetLookupStrategy == null) {
+ throw new ComponentInitializationException("CriteriaSet lookup strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final BaseExpirableStatementContainer<?> responseContainer,
+ @Nullable final MetadataFilterContext filterContext) {
+ checkComponentActive();
+ if (responseContainer == null) {
+ return false;
+ }
+
+ final var statement = responseContainer.getStatement();
+ final String entityId = responseContainer.getEntityId();
+ if (statement != null ) {
+ log.trace("Starting signature validation of statement for {}", entityId);
+ final CriteriaSet criteria = criteriaSetLookupStrategy.apply(responseContainer);
+ if (criteria == null) {
+ log.error("Could not resolve criteria set for {}", entityId);
+ return false;
+ }
+ if (!validateJwt(statement.getJwt(), criteria, entityId)) {
+ return false;
+ }
+ } else {
+ log.debug("Ignoring signature check for the error response for {}", entityId);
+ }
+ return true;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementValidationCriteriaSetLookupFunction.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementValidationCriteriaSetLookupFunction.java
new file mode 100644
index 0000000..4975a90
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultEntityStatementValidationCriteriaSetLookupFunction.java
@@ -0,0 +1,116 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidfed.metadata.BaseJWTWrapper;
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer;
+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 criteria set lookup strategy for {@link BaseExpirableStatementContainer}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultEntityStatementValidationCriteriaSetLookupFunction
+ extends AbstractIdentifiableInitializableComponent
+ implements Function<BaseExpirableStatementContainer<?>, CriteriaSet> {
+
+ /** Class logger. */
+ @Nonnull
+ private Logger log = LoggerFactory.getLogger(DefaultEntityStatementValidationCriteriaSetLookupFunction.class);
+
+ /** Metadata cache used for fetching issuer entity configuration. */
+ @NonnullAfterInit private MetadataCache<EntityConfigurationContainer> entityConfigurationCache;
+
+ /**
+ * Set the metadata cache used for fetching issuer entity configuration.
+ *
+ * @param cache entity configuration cache
+ */
+ public void setEntityConfigurationCache(@Nonnull final MetadataCache<EntityConfigurationContainer> cache) {
+ checkSetterPreconditions();
+ entityConfigurationCache = Constraint.isNotNull(cache, "Entity configuration cache cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (entityConfigurationCache == null) {
+ throw new ComponentInitializationException("Trust Engine cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public CriteriaSet apply(@Nullable final BaseExpirableStatementContainer<?> metadataContainer) {
+ if (metadataContainer != null && metadataContainer.getStatement() != null) {
+ final BaseJWTWrapper<?> statement = metadataContainer.getStatement();
+ assert statement != null;
+ final SubjectStatementCriterion subjectCriterion = new SubjectStatementCriterion(statement);
+ final IssuerEntityStatementCriterion issuerCriterion = getIssuerCriterion(statement);
+ if (issuerCriterion == null) {
+ log.debug("Issuer statement could not be fetched, returning null");
+ return null;
+ }
+ return new CriteriaSet(subjectCriterion, issuerCriterion);
+ }
+
+ log.debug("Entity statement is not found from the container {}, returning null", metadataContainer);
+ return null;
+
+ }
+
+ /**
+ * Fetch the issuer entity configuration from the metadata cache and initialize the criterion.
+ *
+ * @param statement the entity statement whose issuer is fetched
+ * @return the issuer entity configuration, or null if could not be fetched
+ */
+ @Nullable protected IssuerEntityStatementCriterion getIssuerCriterion(
+ @Nonnull final BaseJWTWrapper<?> statement) {
+ final String issuer = statement.getIssuer();
+ final CriteriaSet criteria = new CriteriaSet(new SubjectEntityIDCriterion(issuer));
+ try {
+ final List<EntityConfigurationContainer> result = entityConfigurationCache.get(criteria);
+ if (!result.isEmpty()) {
+ final EntityConfiguration issuerStatement = result.get(0).getStatement();
+ if (issuerStatement != null) {
+ return new IssuerEntityStatementCriterion(issuerStatement);
+ }
+ }
+ } catch (final MetadataCacheException e) {
+ log.debug("Error while fetching issuer entity configuration for {}", issuer, e);
+ }
+ log.warn("Could not fetch entity configuration for {}", issuer);
+ return null;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultPrivateKeyJwtClientAuthenticationFunction.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultPrivateKeyJwtClientAuthenticationFunction.java
new file mode 100644
index 0000000..095846a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultPrivateKeyJwtClientAuthenticationFunction.java
@@ -0,0 +1,317 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.opensaml.messaging.encoder.AbstractMessageEncoder;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.profile.config.JSONSecurityConfiguration;
+import net.shibboleth.oidc.security.impl.JWSTokenSigner;
+import net.shibboleth.oidc.security.jose.SignatureException;
+import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
+import net.shibboleth.oidc.security.jose.SignatureSigningParametersResolver;
+import net.shibboleth.oidc.security.jose.criterion.SignatureSigningConfigurationCriterion;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
+
+/**
+ * Default implementation for function providing private_key_jwt authentication.
+ */
+public class DefaultPrivateKeyJwtClientAuthenticationFunction extends AbstractIdentifiableInitializableComponent
+ implements BiFunction<CriteriaSet,List<String>,BiConsumer<HttpRequest,List<NameValuePair>>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultPrivateKeyJwtClientAuthenticationFunction.class);
+
+ /** Used to log protocol messages. */
+ @Nonnull private Logger protocolMessageLog =
+ LoggerFactory.getLogger(AbstractMessageEncoder.BASE_PROTOCOL_MESSAGE_LOGGER_CATEGORY + ".OAUTH2");
+
+ /** Strategy used to lookup the security configuration used for signing the JWT used in authentication */
+ @NonnullAfterInit private Function<CriteriaSet,JSONSecurityConfiguration> securityConfigurationLookupStrategy;
+
+ /** Strategy used to lookup the type header value for the JWT. */
+ @NonnullAfterInit private Function<CriteriaSet,String> typeHeaderLookupStrategy;
+
+ /** Strategy to find the audience value from the criteria set.*/
+ @NonnullAfterInit private Function<CriteriaSet,String> audienceLookupStrategy;
+
+ /** Strategy to find the issuer/subject value from the criteria set.*/
+ @NonnullAfterInit private Function<CriteriaSet,String> issuerLookupStrategy;
+
+ /** Strategy used to locate the {@link IdentifierGenerationStrategy} to use. */
+ @Nonnull private Function<CriteriaSet,IdentifierGenerationStrategy> idGeneratorLookupStrategy;
+
+ /** Resolver for signing parameters. */
+ @NonnullAfterInit private SignatureSigningParametersResolver resolver;
+
+ /** The offset to add to the 'exp' claim time for JWT client authentication methods. Default is 30 seconds.*/
+ @Nonnull private Duration jwtBearerExpiryOffset;
+
+ /** Object mapper used for pretty-printing JWT contents. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ public DefaultPrivateKeyJwtClientAuthenticationFunction() {
+ idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
+ final Duration thirtySecs = Duration.ofSeconds(30);
+ assert thirtySecs != null;
+ jwtBearerExpiryOffset = thirtySecs;
+ }
+
+ public void setSecurityConfigurationLookupStrategy(
+ @Nonnull final Function<CriteriaSet,JSONSecurityConfiguration> strategy) {
+ checkSetterPreconditions();
+ securityConfigurationLookupStrategy =
+ Constraint.isNotNull(strategy, "The security configuration lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to lookup the type header value for the JWT.
+ *
+ * @param strategy What to set.
+ */
+ public void setTypeHeaderLookupStrategy(@Nonnull final Function<CriteriaSet,String> strategy) {
+ checkSetterPreconditions();
+ typeHeaderLookupStrategy = Constraint.isNotNull(strategy, "The type header lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the audience lookup strategy.
+ *
+ * @param strategy the strategy.
+ */
+ public void setAudienceLookupStrategy(
+ @Nonnull final Function<CriteriaSet, String> strategy) {
+ checkSetterPreconditions();
+ audienceLookupStrategy = Constraint.isNotNull(strategy, "Audience lookup strategy can not be null");
+ }
+
+ /**
+ * Set the issuer/subject lookup strategy.
+ *
+ * @param strategy the strategy.
+ */
+ public void setIssuerLookupStrategy(
+ @Nonnull final Function<CriteriaSet, String> strategy) {
+ checkSetterPreconditions();
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy can not be null");
+ }
+
+ /**
+ * Set the object mapper used for pretty-printing JWT contents.
+ *
+ * @param mapper What to set.
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ checkSetterPreconditions();
+ objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the {@link IdentifierGenerationStrategy} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIdentifierGeneratorLookupStrategy(
+ @Nonnull final Function<CriteriaSet,IdentifierGenerationStrategy> strategy) {
+ checkSetterPreconditions();
+
+ idGeneratorLookupStrategy =
+ Constraint.isNotNull(strategy, "Identifier generation strategy cannot be null");
+ }
+
+ /**
+ * Set the resolver to use for the signing parameters.
+ *
+ * @param newResolver resolver to use
+ */
+ public void setSignatureSigningParametersResolver(
+ @Nonnull final SignatureSigningParametersResolver newResolver) {
+ checkSetterPreconditions();
+
+ resolver = Constraint.isNotNull(newResolver, "SignatureSigningParametersResolver cannot be null");
+ }
+
+ /**
+ * Set the JWT expiry time offset for client authentications.
+ *
+ * @param expiry the JWT 'exp' claim offset
+ */
+ public void setJwtBearerExpiryOffset(@Nonnull final Duration expiry) {
+ checkSetterPreconditions();
+
+ jwtBearerExpiryOffset = Constraint.isNotNull(expiry, "jwtBearerExpiryOffset can not be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (securityConfigurationLookupStrategy == null) {
+ throw new ComponentInitializationException("The security configuration lookup strategy cannot be null");
+ }
+ if (typeHeaderLookupStrategy == null) {
+ throw new ComponentInitializationException("The type header lookup strategy cannot be null");
+ }
+ if (audienceLookupStrategy == null) {
+ throw new ComponentInitializationException("The audience lookup strategy cannot be null)");
+ }
+ if (issuerLookupStrategy == null) {
+ throw new ComponentInitializationException("The issuer lookup strategy cannot be null)");
+ }
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("Object mapper cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public BiConsumer<HttpRequest,List<NameValuePair>> apply(@Nullable final CriteriaSet criteria,
+ @Nullable final List<String> methods) {
+ checkComponentActive();
+ if (methods == null || !methods.contains("private_key_jwt")) {
+ log.debug("No supported client authentication methods in {}, nothing to do", methods);
+ return null;
+ }
+ final JSONSecurityConfiguration securityConfiguration = securityConfigurationLookupStrategy.apply(criteria);
+ if (securityConfiguration == null || securityConfiguration.getJwtSignatureSigningConfiguration() == null) {
+ log.error("Could not resolve signature signing configuration");
+ return null;
+ }
+
+ final SignatureSigningParameters signingParameters;
+ try {
+ final CriteriaSet resolverCriteria = criteria == null ? new CriteriaSet() : criteria;
+ resolverCriteria.add(new SignatureSigningConfigurationCriterion(
+ securityConfiguration.getJwtSignatureSigningConfiguration()));
+ signingParameters = resolver.resolveSingle(resolverCriteria);
+ if (signingParameters == null) {
+ log.error("Failed to resolve signature signing parameters");
+ return null;
+ }
+ log.debug("Signature signing parameters successfully resolved");
+ } catch (final ResolverException e) {
+ log.error("Error resolving signature signing parameters", e);
+ return null;
+ }
+
+ final String typeHeader = typeHeaderLookupStrategy.apply(criteria);
+ final String audience = audienceLookupStrategy.apply(criteria);
+ if (StringSupport.trimOrNull(audience) == null) {
+ log.warn("Could not resolve audience for the JWT");
+ return null;
+ }
+ final String issuer = issuerLookupStrategy.apply(criteria);
+ if (StringSupport.trimOrNull(issuer) == null) {
+ log.warn("Could not resolve issuer/subject for the JWT");
+ return null;
+ }
+ final IdentifierGenerationStrategy idGenerator = idGeneratorLookupStrategy.apply(criteria);
+ if (idGenerator == null) {
+ log.error("Could not resolve identifier generator for JWT");
+ return null;
+ }
+ assert issuer != null; assert audience != null;
+ final SignedJWT jwt = constructJWT(idGenerator.generateIdentifier(), issuer, audience, signingParameters,
+ typeHeader);
+ if (jwt == null) {
+ return null;
+ }
+ return new BiConsumer<HttpRequest,List<NameValuePair>>() {
+
+ /** {@inheritDoc} */
+ @Override
+ public void accept(@Nullable final HttpRequest httpRequest,
+ @Nullable final List<NameValuePair> parameters) {
+ if (httpRequest == null || parameters == null) {
+ return;
+ }
+ parameters.add(new BasicNameValuePair(
+ "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"));
+ parameters.add(new BasicNameValuePair("client_assertion", jwt.serialize()));
+ }
+
+ };
+ }
+
+ /**
+ * Construct a {@link SignedJWT} with the given input claims and signing parameters.
+ * @param issuer The issuer for the JWT
+ * @param audience The audience for the JWT
+ * @param signingParameters The signing parameters
+ * @param typeHeader The type header value for the JWT
+ * @return A signed JWT, or null
+ */
+ @Nullable protected SignedJWT constructJWT(@Nonnull @NotEmpty final String jti,
+ @Nonnull @NotEmpty final String issuer, @Nonnull @NotEmpty final String audience,
+ @Nonnull final SignatureSigningParameters signingParameters, @Nullable final String typeHeader) {
+ final JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder()
+ .subject(issuer)
+ .issuer(issuer)
+ .audience(audience)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plus(jwtBearerExpiryOffset)))
+ .jwtID(jti)
+ .build();
+ assert jwtClaims != null;
+ final JWSTokenSigner signer = new JWSTokenSigner(signingParameters);
+ // TODO: log the payload contents - perhaps move the OP's ResponseUtil into java-oidc-common (?)
+ /*
+ try {
+ assert objectMapper != null;
+ // TODO: log the payload contents - perhaps move the OP's ResponseUtil into java-oidc-common (?)
+ // protocolMessageLog.trace("JWT client authentication payload contents:\n{}",
+ // ResponseUtil.getJwtProtocolMessage(jwtClaims, objectMapper));
+ } catch (final ParseException e) {
+ log.error("Could not construct the protocol message of the JWT contents", e);
+ }
+ */
+ try {
+ return signer.sign(jwtClaims, typeHeader);
+ } catch (final SignatureException e) {
+ log.error("Could not sign the JWT", e);
+ }
+ return null;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultResponseContainerExpirationTimeStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultResponseContainerExpirationTimeStrategy.java
new file mode 100644
index 0000000..22a78c7
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultResponseContainerExpirationTimeStrategy.java
@@ -0,0 +1,47 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.time.Instant;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import net.shibboleth.oidc.metadata.cache.ExpirationTimeContext;
+
+/**
+ * Default strategy for fetching expiration time for the response containers extending {@link
+ * BaseExpirableMetadataContainer}. The expiration instant is fetched from which is before: the instant returned by
+ * {@link BaseExpirableMetadataContainer#getExpirationInstant()} or {@link ExpirationTimeContext#getMaxCacheDuration()}.
+ */
+ at ThreadSafe
+public class DefaultResponseContainerExpirationTimeStrategy
+ implements Function<ExpirationTimeContext<BaseExpirableMetadataContainer>, Instant> {
+
+ /** {@inheritDoc} */
+ @Nullable public Instant apply(@Nullable final ExpirationTimeContext<BaseExpirableMetadataContainer> context) {
+ if (context == null) {
+ return null;
+ }
+ final Instant contextExpiration = context.getNow().plus(context.getMaxCacheDuration());
+ final BaseExpirableMetadataContainer container = context.getMetadata();
+ if (container == null) {
+ return contextExpiration;
+ }
+ final Instant containerExpiration = container.getExpirationInstant();
+ return containerExpiration.isBefore(contextExpiration) ? containerExpiration : contextExpiration;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultTrustChainHeaderValidationCondition.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultTrustChainHeaderValidationCondition.java
new file mode 100644
index 0000000..8abc52e
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/DefaultTrustChainHeaderValidationCondition.java
@@ -0,0 +1,196 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.util.EntityStatementHelper;
+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;
+
+/**
+ * Default validator that validates the syntax of trust_chain and peer_trust_chain headers if they're allowed to be
+ * used with the given entity statement {@link SignedJWT}.
+ */
+public class DefaultTrustChainHeaderValidationCondition extends AbstractIdentifiableInitializableComponent
+ implements BiPredicate<SignedJWT, ProfileRequestContext> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainHeaderValidationCondition.class);
+
+ /** JSON object mapper used for decoding entity statement payload. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /** The flag to indicate allowing the use of trust_chain header. */
+ private boolean allowTrustChainHeader = false;
+
+ /** The flag to indicate allowing the use of peer_trust_chain header. */
+ private boolean allowPeerTrustChainHeader = false;
+
+ /** The flag to indicate requiring the same trust anchor if both headers are present. */
+ private boolean requireCommonTrustAnchor = true;
+
+ /**
+ * Set the JSON {@link ObjectMapper} used for decoding entity statement payload.
+ *
+ * @param mapper object mapper
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ checkSetterPreconditions();
+ objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+ }
+
+ /**
+ * Set the flag to indicate allowing the use of trust_chain header.
+ *
+ * @param flag value
+ */
+ public void setAllowTrustChainHeader(final boolean flag) {
+ checkSetterPreconditions();
+ allowTrustChainHeader = flag;
+ }
+
+ /**
+ * Set the flag to indicate allowing the use of peer_trust_chain header.
+ *
+ * @param flag value
+ */
+ public void setAllowPeerTrustChainHeader(final boolean flag) {
+ checkSetterPreconditions();
+ allowPeerTrustChainHeader = flag;
+ }
+
+ /**
+ * Set the flag to indicate requiring the same trust anchor if both headers are present.
+ *
+ * @param flag value
+ */
+ public void setRequireCommonTrustAnchor(final boolean flag) {
+ checkSetterPreconditions();
+ requireCommonTrustAnchor = flag;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("ObjectMapper cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final SignedJWT signedJwt,
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ checkComponentActive();
+ if (signedJwt == null || signedJwt.getHeader() == null || profileRequestContext == null) {
+ log.error("Invalid input: signed JWT nor profile request context cannot be null");
+ return false;
+ }
+ final JWTClaimsSet claimsSet;
+ try {
+ claimsSet = signedJwt.getJWTClaimsSet();
+ } catch (final ParseException e) {
+ log.warn("Could not parse entity statement claims set", e);
+ return false;
+ }
+ final JWSHeader header = signedJwt.getHeader();
+ assert header != null;
+ final Set<String> params = header.getIncludedParams();
+ final List<EntityStatement<?>> trustChain;
+ if (params.contains("trust_chain")) {
+ if (!allowTrustChainHeader) {
+ log.warn("The use of trust_chain header is not allowed for {}", claimsSet.getSubject());
+ return false;
+ }
+ trustChain = parseToList(header.getCustomParam("trust_chain"), profileRequestContext);
+ if (trustChain == null) {
+ log.warn("Could not parse trust_chain contents into a trust chain");
+ return false;
+ }
+ } else {
+ trustChain = null;
+ }
+
+ final List<EntityStatement<?>> peerTrustChain;
+ if (params.contains("peer_trust_chain")) {
+ if (!allowPeerTrustChainHeader) {
+ log.warn("The use of peer_trust_chain header is not allowed for {}", claimsSet.getSubject());
+ return false;
+ }
+ peerTrustChain = parseToList(header.getCustomParam("peer_trust_chain"), profileRequestContext);
+ if (peerTrustChain == null) {
+ log.warn("Could not parse peer_trust_chain contents into a trust chain");
+ return false;
+ }
+ } else {
+ peerTrustChain = null;
+ }
+
+ if (trustChain != null && peerTrustChain != null) {
+ if (requireCommonTrustAnchor) {
+ log.debug("Both trust_chain and peer_trust_chain exists, verifying the trust anchors match");
+ final String trustChainAnchor = trustChain.get(trustChain.size() - 1).getSubject();
+ final String peerTrustChainAnchor = peerTrustChain.get(peerTrustChain.size() - 1).getSubject();
+ if (!trustChainAnchor.equals(peerTrustChainAnchor)) {
+ log.warn("The trust_chain anchor {} does not match with the peer_trust_chain anchor {}",
+ trustChainAnchor, peerTrustChainAnchor);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Parse the raw object to trust chain.
+ *
+ * @param input raw object
+ * @param profileRequestContext profile request context
+ * @return trust chain if the input could be parsed, null otherwise
+ */
+ @Nullable protected List<EntityStatement<?>> parseToList(@Nullable final Object input,
+ @Nonnull final ProfileRequestContext profileRequestContext) {
+ if (input instanceof List<?> list) {
+ final List<String> strings = list.stream()
+ .filter(String.class::isInstance)
+ .map(String.class::cast)
+ .toList();
+ assert strings != null; assert objectMapper != null;
+ return EntityStatementHelper.deserializeTrustChain(strings, objectMapper);
+ }
+ return null;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/FederationEndpointEntityStatementCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/FederationEndpointEntityStatementCriterion.java
new file mode 100644
index 0000000..109d839
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/FederationEndpointEntityStatementCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing an entity statement of an entity offering federation API endpoints.
+ */
+public class FederationEndpointEntityStatementCriterion implements Criterion {
+
+ /** The entity statement value. */
+ @Nonnull final EntityStatement<?> value;
+
+ /**
+ * Constructor.
+ *
+ * @param statement the entity statement value, must not be null
+ */
+ public FederationEndpointEntityStatementCriterion(@Nonnull final EntityStatement<?> statement) {
+ value = Constraint.isNotNull(statement, "Entity statement cannot be null");
+ }
+
+ /**
+ * Get the entity statement value.
+ *
+ * @return the entity statement value
+ */
+ @Nonnull public EntityStatement<?> getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "FederationEndpointEntityStatementCriterion [value=" + value.getJwt().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 FederationEndpointEntityStatementCriterion other = (FederationEndpointEntityStatementCriterion) obj;
+ return value.getJwt().serialize().equals(other.value.getJwt().serialize());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/IssuerEntityIDCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/IssuerEntityIDCriterion.java
new file mode 100644
index 0000000..cb9ce98
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/IssuerEntityIDCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing an issuer entity ID in entity statement.
+ */
+public class IssuerEntityIDCriterion implements Criterion {
+
+ /** The entity ID value. */
+ @Nonnull @NotEmpty final String value;
+
+ /**
+ * Constructor.
+ *
+ * @param entityId the entity ID value, must not be null
+ */
+ public IssuerEntityIDCriterion(@Nonnull @NotEmpty final String entityId) {
+ value = Constraint.isNotEmpty(entityId, "Entity ID cannot be null");
+ }
+
+ /**
+ * Get the entity ID value.
+ *
+ * @return the entity ID value
+ */
+ @Nonnull @NotEmpty public String getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "IssuerEntityIDCriterion [value=" + value + "]";
+ }
+
+ /** {@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 IssuerEntityIDCriterion other = (IssuerEntityIDCriterion) obj;
+ return value.equals(other.value);
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/IssuerEntityStatementCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/IssuerEntityStatementCriterion.java
new file mode 100644
index 0000000..69508f2
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/IssuerEntityStatementCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a subject entity statement.
+ */
+public class IssuerEntityStatementCriterion implements Criterion {
+
+ /** The entity statement value. */
+ @Nonnull final EntityStatement<?> value;
+
+ /**
+ * Constructor.
+ *
+ * @param statement the entity statement value, must not be null
+ */
+ public IssuerEntityStatementCriterion(@Nonnull final EntityStatement<?> statement) {
+ value = Constraint.isNotNull(statement, "Entity statement cannot be null");
+ }
+
+ /**
+ * Get the entity statement value.
+ *
+ * @return the entity statement value
+ */
+ @Nonnull public EntityStatement<?> getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "IssuerEntityStatementCriterion [value=" + value.getJwt().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 IssuerEntityStatementCriterion other = (IssuerEntityStatementCriterion) obj;
+ return value.getJwt().serialize().equals(other.value.getJwt().serialize());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/PreSelectedTrustChainCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/PreSelectedTrustChainCriterion.java
new file mode 100644
index 0000000..3ecec42
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/PreSelectedTrustChainCriterion.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.oidfed.metadata.cache;
+
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing entity IDs for a pre-selected trust chain.
+ */
+public class PreSelectedTrustChainCriterion implements Criterion {
+
+ /** The entity ID values. */
+ @Nonnull @NotEmpty private final List<String> value;
+
+ /**
+ * Constructor.
+ *
+ * @param entityIds the entity ID values, must not be null
+ */
+ public PreSelectedTrustChainCriterion(@Nonnull final List<String> entityIds) {
+ value = Constraint.isNotNull(entityIds, "Entity ID cannot be null");
+ }
+
+ /**
+ * Get the entity ID values.
+ *
+ * @return the entity ID values
+ */
+ @Nonnull public List<String> getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "PreSelectedTrustChainCriterion [value=" + value + "]";
+ }
+
+ /** {@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 PreSelectedTrustChainCriterion other = (PreSelectedTrustChainCriterion) obj;
+ return value.containsAll(other.value) && other.value.containsAll(value);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/ResponseContainerExpirationCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/ResponseContainerExpirationCriterion.java
new file mode 100644
index 0000000..a6fe52a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/ResponseContainerExpirationCriterion.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.oidfed.metadata.cache;
+
+import java.time.Instant;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing expiration instant for a response message container.
+ */
+public class ResponseContainerExpirationCriterion implements Criterion {
+
+ /** The expiration instant. */
+ @Nonnull private final Instant instant;
+
+ /**
+ * Constructor.
+ *
+ * @param expirationInstant expiration instant, must not be null
+ */
+ public ResponseContainerExpirationCriterion(@Nonnull final Instant expirationInstant) {
+ instant = Constraint.isNotNull(expirationInstant, "Expiration instant cannot be null");
+ }
+
+ /**
+ * Get the expiration instant.
+ *
+ * @return the expiration instant
+ */
+ @Nonnull public Instant getExpirationInstant() {
+ return instant;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "ResolveEntityContainerExpirationCriterion [instant=" + instant.toEpochMilli() + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(instant);
+ }
+
+ /** {@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 ResponseContainerExpirationCriterion other = (ResponseContainerExpirationCriterion) obj;
+ return instant.equals(other.instant);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SignatureValidationKeyContainerJwtCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SignatureValidationKeyContainerJwtCriterion.java
new file mode 100644
index 0000000..4963287
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SignatureValidationKeyContainerJwtCriterion.java
@@ -0,0 +1,101 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.Objects;
+
+import javax.annotation.Nullable;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * An implementation of {@link Criterion} which specifies criteria based on the contents of a {@link SignedJWT} element
+ * containing signature validation keys.
+ */
+public final class SignatureValidationKeyContainerJwtCriterion implements Criterion {
+
+ /** The JWT which serves as the source for credential criteria. */
+ @Nullable private SignedJWT jwt;
+
+ /**
+ * Constructor.
+ *
+ * @param keyContainer the key container criteria to use
+ */
+ public SignatureValidationKeyContainerJwtCriterion(@Nullable final SignedJWT keyContainer) {
+ setJwt(keyContainer);
+ }
+
+ /**
+ * Gets the JWT which is the source of credential criteria.
+ *
+ * @return the JWT credential criteria
+ */
+ @Nullable public SignedJWT getJwt() {
+ return jwt;
+ }
+
+ /**
+ * Sets the JWT which is the source of credential criteria.
+ *
+ * @param keyContainer the key container criteria to use
+ *
+ */
+ public void setJwt(@Nullable final SignedJWT keyContainer) {
+ // Note: we allow JOSEObject to be null to handle case where application context,
+ // other accompanying criteria, etc should be used to resolve credentials.
+ jwt = keyContainer;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("SignatureValidationKeyContainerJwtCriterion [jwt=");
+ builder.append("<contents not displayable>");
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ if (jwt != null) {
+ return jwt.hashCode();
+ }
+ return super.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj instanceof SignatureValidationKeyContainerJwtCriterion other) {
+ return Objects.equals(jwt, other.jwt);
+ }
+
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectEntityIDCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectEntityIDCriterion.java
new file mode 100644
index 0000000..5a606b6
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectEntityIDCriterion.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.oidfed.metadata.cache;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a subject entity ID in entity statement.
+ */
+public class SubjectEntityIDCriterion implements Criterion {
+
+ /** The entity ID value. */
+ @Nonnull @NotEmpty private final String value;
+
+ /**
+ * Constructor.
+ *
+ * @param entityId the entity ID value, must not be null
+ */
+ public SubjectEntityIDCriterion(@Nonnull @NotEmpty final String entityId) {
+ value = Constraint.isNotEmpty(entityId, "Entity ID cannot be null");
+ }
+
+ /**
+ * Get the entity ID value.
+ *
+ * @return the entity ID value
+ */
+ @Nonnull @NotEmpty public String getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "SubjectEntityIDCriterion [value=" + value + "]";
+ }
+
+ /** {@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 SubjectEntityIDCriterion other = (SubjectEntityIDCriterion) obj;
+ return value.equals(other.value);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectEntityStatementCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectEntityStatementCriterion.java
new file mode 100644
index 0000000..82687b1
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectEntityStatementCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a subject entity statement.
+ */
+public class SubjectEntityStatementCriterion implements Criterion {
+
+ /** The entity statement value. */
+ @Nonnull final EntityStatement<?> value;
+
+ /**
+ * Constructor.
+ *
+ * @param statement the entity statement value, must not be null
+ */
+ public SubjectEntityStatementCriterion(@Nonnull final EntityStatement<?> statement) {
+ value = Constraint.isNotNull(statement, "Entity statement cannot be null");
+ }
+
+ /**
+ * Get the entity statement value.
+ *
+ * @return the entity statement value
+ */
+ @Nonnull public EntityStatement<?> getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "SubjectEntityStatementCriterion [value=" + value.getJwt().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 SubjectEntityStatementCriterion other = (SubjectEntityStatementCriterion) obj;
+ return value.getJwt().serialize().equals(other.value.getJwt().serialize());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectLocallyTrustedKeysCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectLocallyTrustedKeysCriterion.java
new file mode 100644
index 0000000..4adb38c
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectLocallyTrustedKeysCriterion.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.oidfed.metadata.cache;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing locally trusted keys for a subject.
+ */
+public class SubjectLocallyTrustedKeysCriterion implements Criterion {
+
+ /** The locally trusted keys. */
+ @Nonnull final JWKSet value;
+
+ /**
+ * Constructor.
+ *
+ * @param jwkSet the locally trusted key set, must not be null
+ */
+ public SubjectLocallyTrustedKeysCriterion(@Nonnull final JWKSet jwkSet) {
+ value = Constraint.isNotNull(jwkSet, "JWKSet cannot be null");
+ }
+
+ /**
+ * Get the locally trusted keys.
+ *
+ * @return the locally rusted keys
+ */
+ @Nonnull public JWKSet getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "SubjectLocallyTrustedKeysCriterion [value=" + value.toString() + "]";
+ }
+
+ /** {@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 SubjectLocallyTrustedKeysCriterion other = (SubjectLocallyTrustedKeysCriterion) obj;
+ return value.toString().equals(other.value.toString());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectStatementCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectStatementCriterion.java
new file mode 100644
index 0000000..8e7b5fc
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/SubjectStatementCriterion.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.oidfed.metadata.cache;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.BaseJWTWrapper;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a subject statement.
+ */
+public class SubjectStatementCriterion implements Criterion {
+
+ /** The statement value. */
+ @Nonnull final BaseJWTWrapper<?> value;
+
+ /**
+ * Constructor.
+ *
+ * @param statement the statement value, must not be null
+ */
+ public SubjectStatementCriterion(
+ @Nonnull final BaseJWTWrapper<?> statement) {
+ value = Constraint.isNotNull(statement, "Entity statement cannot be null");
+ }
+
+ /**
+ * Get the statement value.
+ *
+ * @return the statement value
+ */
+ @Nonnull public BaseJWTWrapper<?> getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "SubjectStatementCriterion [value=" + value.getJwt().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 SubjectStatementCriterion other = (SubjectStatementCriterion) obj;
+ return value.getJwt().serialize().equals(other.value.getJwt().serialize());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/TrustAnchorEntityIDsCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/TrustAnchorEntityIDsCriterion.java
new file mode 100644
index 0000000..f55fb8a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/TrustAnchorEntityIDsCriterion.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.oidfed.metadata.cache;
+
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing trust anchor entity IDs in resolve entity request.
+ */
+public class TrustAnchorEntityIDsCriterion implements Criterion {
+
+ /** The entity ID values. */
+ @Nonnull final List<String> values;
+
+ /**
+ * Constructor.
+ *
+ * @param entityIds the entity ID values, must not be null nor empty
+ */
+ public TrustAnchorEntityIDsCriterion(@Nonnull @NotEmpty final List<String> entityIds) {
+ Constraint.isNotEmpty(entityIds, "Entity IDs cannot be null nor empty");
+ values = entityIds;
+ }
+
+ /**
+ * Get the entity ID values.
+ *
+ * @return the entity ID values
+ */
+ @Nonnull @NotEmpty public List<String> getValues() {
+ return values;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "TrustAnchorEntityIDsCriterion [values=" + values + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(values);
+ }
+
+ /** {@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 TrustAnchorEntityIDsCriterion other = (TrustAnchorEntityIDsCriterion) obj;
+ return values.containsAll(other.values) && other.values.containsAll(values);
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/TrustMarkCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/TrustMarkCriterion.java
new file mode 100644
index 0000000..a775366
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/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.oidfed.metadata.cache;
+
+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/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/TrustMarkOwnersCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/TrustMarkOwnersCriterion.java
new file mode 100644
index 0000000..44e3365
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/TrustMarkOwnersCriterion.java
@@ -0,0 +1,81 @@
+/*
+ * 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.oidfed.metadata.cache;
+
+import java.util.Map;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.payload.claim.TrustMarkOwner;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing trust mark owners.
+ */
+public class TrustMarkOwnersCriterion implements Criterion {
+
+ /** The trust mark owners. */
+ @Nonnull private final Map<String, TrustMarkOwner> owners;
+
+ /**
+ * Constructor.
+ *
+ * @param trustMarkOwners the truts mark owners, must not be null
+ */
+ public TrustMarkOwnersCriterion(@Nonnull final Map<String, TrustMarkOwner> trustMarkOwners) {
+ owners = Constraint.isNotNull(trustMarkOwners, "Trust Mark owners cannot be null");
+ }
+
+ /**
+ * Get the trust mark owners value.
+ *
+ * @return the trust mark owners value
+ */
+ @Nonnull
+ public Map<String, TrustMarkOwner> getValue() {
+ return owners;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "TrustMarkOwnersCriterion [owners=" + owners + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(owners);
+ }
+
+ /** {@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 TrustMarkOwnersCriterion other = (TrustMarkOwnersCriterion) obj;
+ return owners.equals(other.owners);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationCriteriaSetLookupFunction.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationCriteriaSetLookupFunction.java
new file mode 100644
index 0000000..ecb4a29
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationCriteriaSetLookupFunction.java
@@ -0,0 +1,47 @@
+/*
+ * 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.oidfed.metadata.cache.configuration;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityStatementCriterion;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default criteria set lookup strategy for entity configurations.
+ */
+ at ThreadSafe
+public class DefaultEntityConfigurationCriteriaSetLookupFunction
+ implements Function<BaseExpirableStatementContainer<?>, CriteriaSet> {
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public CriteriaSet apply(@Nullable final BaseExpirableStatementContainer<?> metadataContainer) {
+ return Optional.ofNullable(metadataContainer)
+ .map(container -> container.getStatement())
+ .filter(EntityConfiguration.class::isInstance)
+ .map(EntityConfiguration.class::cast)
+ .map(statement -> statement != null ?
+ new CriteriaSet(new SubjectEntityStatementCriterion(statement)) : null)
+ .orElse(null);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java
new file mode 100644
index 0000000..201dab2
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java
@@ -0,0 +1,138 @@
+/*
+ * 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.oidfed.metadata.cache.configuration;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.net.URIBuilder;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.cache.AbstractFederationEndpointResponseFetchingStrategy;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityIDCriterion;
+import net.shibboleth.oidfed.metadata.impl.EntityConfigurationImpl;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for fetching entity configuration for the request specified in the criteria set. The parsed
+ * response details are stored inside a {@link EntityConfigurationContainer}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultEntityConfigurationFetchingStrategy
+ extends AbstractFederationEndpointResponseFetchingStrategy<String, EntityConfigurationContainer> {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("entity-statement+jwt");
+
+ /** The HTTP response content type. */
+ @Nonnull public static final String HTTP_RESPONSE_CONTENT_TYPE = "application/" + JWT_TYPE_HEADER.toString();
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultEntityConfigurationFetchingStrategy.class);
+
+ /**
+ * Constructor.
+ */
+ public DefaultEntityConfigurationFetchingStrategy() {
+ setCriteriaToRequestDataStrategy(criteria -> {
+ final SubjectEntityIDCriterion criterion = criteria.get(SubjectEntityIDCriterion.class);
+ if (criterion == null) {
+ log.debug("No SubjectEntityIDCriterion, returning null");
+ return null;
+ }
+ return criterion.getValue();
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+ @Nonnull final String entityId) {
+ final HttpGet httpRequest = new HttpGet(entityId);
+ try {
+ final String path = httpRequest.getPath();
+ final URI uri =
+ new URIBuilder(httpRequest.getUri()).setPath(
+ (path.endsWith("/") ? path : path.concat("/"))
+ .concat(".well-known/openid-federation")).build();
+ httpRequest.setUri(uri);
+ } catch (final URISyntaxException e) {
+ log.error("Could not create URI with the given parameters {}", entityId, e);
+ }
+ return httpRequest;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected EntityConfigurationContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
+ @Nonnull final String entityId, @Nullable final ClassicHttpResponse response,
+ @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+ @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+ if (response != null) {
+ if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
+ log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+ return new EntityConfigurationContainer(entityId, null, validExpiration, invalidExpiration);
+ }
+
+ try {
+ final String content = EntityUtils.toString(response.getEntity());
+ log.trace("Attempting to parse signed JWT from content: {}", content);
+ final SignedJWT jwt = SignedJWT.parse(content);
+ if (!JWT_TYPE_HEADER.equals(jwt.getHeader().getType())) {
+ log.warn("Unexpected JWT type header {}", jwt.getHeader().getType());
+ return new EntityConfigurationContainer(entityId, null, validExpiration, invalidExpiration);
+ }
+ assert objectMapper != null;
+ final EntityConfiguration entityConfiguration = EntityConfigurationImpl.parse(jwt, objectMapper);
+ return new EntityConfigurationContainer(
+ entityId, entityConfiguration, validExpiration, invalidExpiration);
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not deserialize entity statement payload from the response", e);
+ } catch (final ConstraintViolationException e) {
+ log.warn("Parsed entity configuration claims did not pass syntax validation", e);
+ } catch (java.text.ParseException e) {
+ log.warn("Could not parse JWT from the response", e);
+ }
+ return new EntityConfigurationContainer(entityId, null, validExpiration, invalidExpiration);
+ } else {
+ log.debug("Unable to fetch entity configuration for: {} (null response)", entityId);
+ return new EntityConfigurationContainer(entityId, null, validExpiration, nullExpiration);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected EntityConfigurationContainer handleException(@Nonnull final CriteriaSet criteria,
+ @Nonnull final String entityId, @Nonnull final Throwable throwable, @Nonnull final Instant expiration) {
+ return new EntityConfigurationContainer(entityId, null, expiration, expiration);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/EntityConfigurationContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/EntityConfigurationContainer.java
new file mode 100644
index 0000000..40ebc0f
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/EntityConfigurationContainer.java
@@ -0,0 +1,59 @@
+/*
+ * 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.oidfed.metadata.cache.configuration;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A container class for metadata caches carrying entity ID and statement details related to Entity Configuration.
+ */
+public class EntityConfigurationContainer extends BaseExpirableStatementContainer<EntityConfiguration>
+ implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 756269369356865370L;
+
+ /** Requested entity ID. */
+ @Nonnull @NotEmpty private final String entityId;
+
+ /**
+ * Constructor.
+ *
+ * @param entity entity ID, must not be null
+ * @param configuration entity configuration parsed from the response
+ * @param validExpirationInstant expiration instant for valid statement, must not be null
+ * @param invalidExpirationInstant expiration instant for invalid statement, must not be null
+ */
+ public EntityConfigurationContainer(@Nonnull @NotEmpty final String entity,
+ @Nullable final EntityConfiguration configuration, @Nonnull final Instant validExpirationInstant,
+ @Nonnull final Instant invalidExpirationInstant) {
+ super(validExpirationInstant, configuration, invalidExpirationInstant);
+ entityId = Constraint.isNotNull(entity, "Entity ID cannot be empty");
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getEntityId() {
+ return entityId;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/ProvidedEntityConfigurationContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/ProvidedEntityConfigurationContainer.java
new file mode 100644
index 0000000..4f3d5cc
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/ProvidedEntityConfigurationContainer.java
@@ -0,0 +1,54 @@
+/*
+ * 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.oidfed.metadata.cache.configuration;
+
+import java.time.Instant;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+
+/**
+ * Metadata cache container for provided entity configuration.
+ */
+public class ProvidedEntityConfigurationContainer extends EntityConfigurationContainer {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -6322735237721678470L;
+
+ /**
+ * Constructor.
+ *
+ * @param configuration provided entity configuration
+ */
+ public ProvidedEntityConfigurationContainer(@Nonnull final EntityConfiguration configuration) {
+ super(configuration.getSubject(), configuration, parseExpirationInstant(configuration),
+ parseExpirationInstant(configuration));
+ }
+
+ /**
+ * Parse expiration instant from entity configuration.
+ *
+ * @param configuration entity configuration
+ * @return expiration set in configuration, or 5 minutes from now if it was not set
+ */
+ @Nonnull private static Instant parseExpirationInstant(@Nonnull final EntityConfiguration configuration) {
+ final Instant expiration = Optional.ofNullable(configuration.getParsedPayload().getExpiration())
+ .orElse(Instant.now().plusSeconds(300));
+ assert expiration != null;
+ return expiration;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/ProvidedEntityConfigurationContainerCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/ProvidedEntityConfigurationContainerCriterion.java
new file mode 100644
index 0000000..cd43898
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/ProvidedEntityConfigurationContainerCriterion.java
@@ -0,0 +1,88 @@
+/*
+ * 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.oidfed.metadata.cache.configuration;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a provided entity configuration container.
+ */
+public class ProvidedEntityConfigurationContainerCriterion implements Criterion {
+
+ /** The entity configuration container value. */
+ @Nonnull final ProvidedEntityConfigurationContainer value;
+
+ /**
+ * Constructor.
+ *
+ * @param container the entity configuration container value, must not be null
+ */
+ public ProvidedEntityConfigurationContainerCriterion(
+ @Nonnull final ProvidedEntityConfigurationContainer container) {
+ value = Constraint.isNotNull(container, "Entity configuration container cannot be null");
+ }
+
+ /**
+ * Get the entity configuration container value.
+ *
+ * @return the container value
+ */
+ @Nonnull public ProvidedEntityConfigurationContainer getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final var statement = value.getStatement();
+ return "ProvidedEntityConfigurationContainerCriterion [value="
+ + (statement == null ? "null" : statement.getJwt().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 ProvidedEntityConfigurationContainerCriterion other = (ProvidedEntityConfigurationContainerCriterion) obj;
+ final var statement = value.getStatement();
+ final var otherStatement = other.value.getStatement();
+ if (statement == null) {
+ return otherStatement == null;
+ } else {
+ return statement.getJwt().serialize().equals(otherStatement == null ?
+ null : otherStatement.getJwt().serialize());
+ }
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetCriteriaSetLookupFunction.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetCriteriaSetLookupFunction.java
new file mode 100644
index 0000000..72f5d1b
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetCriteriaSetLookupFunction.java
@@ -0,0 +1,49 @@
+/*
+ * 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.oidfed.metadata.cache.keyset;
+
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import net.shibboleth.oidfed.metadata.SignedKeyset;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityStatementCriterion;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default criteria set lookup strategy for signed keyset.
+ */
+ at ThreadSafe
+public class DefaultSignedKeysetCriteriaSetLookupFunction
+ implements Function<BaseExpirableStatementContainer<?>, CriteriaSet> {
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public CriteriaSet apply(@Nullable final BaseExpirableStatementContainer<?> metadataContainer) {
+ if (metadataContainer instanceof SignedKeysetContainer keysetContainer) {
+ final CriteriaSet result = new CriteriaSet(
+ new SubjectEntityStatementCriterion(keysetContainer.getIdentifier().getEntityConfiguration()));
+ final SignedKeyset keyset = keysetContainer.getStatement();
+ if (keyset != null) {
+ result.add(new SubjectSignedKeysetCriterion(keyset));
+ }
+ return result;
+ }
+ return null;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetFetchingStrategy.java
new file mode 100644
index 0000000..5b38802
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetFetchingStrategy.java
@@ -0,0 +1,145 @@
+/*
+ * 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.oidfed.metadata.cache.keyset;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.SignedKeyset;
+import net.shibboleth.oidfed.metadata.cache.AbstractFederationEndpointResponseFetchingStrategy;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityStatementCriterion;
+import net.shibboleth.oidfed.metadata.impl.SignedKeysetImpl;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for fetching signed keyset for the request specified in the criteria set. The parsed response
+ * details are stored inside a {@link SignedKeysetContainer}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultSignedKeysetFetchingStrategy
+ extends AbstractFederationEndpointResponseFetchingStrategy<SignedKeysetCacheIdentifier, SignedKeysetContainer> {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("jwk-set+jwt");
+
+ /** The HTTP response content type. */
+ @Nonnull public static final String HTTP_RESPONSE_CONTENT_TYPE = "application/" + JWT_TYPE_HEADER.toString();
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultSignedKeysetFetchingStrategy.class);
+
+ /**
+ * Constructor.
+ */
+ public DefaultSignedKeysetFetchingStrategy() {
+ setCriteriaToRequestDataStrategy(criteria -> {
+ final SubjectEntityStatementCriterion subjectCriterion =
+ criteria.get(SubjectEntityStatementCriterion.class);
+ if (subjectCriterion == null) {
+ log.debug("No SubjectEntityStatementCriterion, returning null");
+ return null;
+ }
+ final SubjectSignedKeysetUriCriterion uriCriterion = criteria.get(SubjectSignedKeysetUriCriterion.class);
+ if (uriCriterion == null) {
+ log.debug("No SubjectSignedKeysetUriCriterion, returning null");
+ return null;
+ }
+ if (subjectCriterion.getValue() instanceof EntityConfiguration entityConfiguration) {
+ return new SignedKeysetCacheIdentifier(entityConfiguration, uriCriterion.getValue());
+ }
+ log.debug("SubjectEntityStatementCriterion did not contain entity configuration, returning null");
+ return null;
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+ @Nonnull final SignedKeysetCacheIdentifier identifier) {
+ try {
+ return new HttpGet(new URI(identifier.getUri()));
+ } catch (final URISyntaxException e) {
+ log.error("Could not create URI for {} with the given parameters {}",
+ identifier.getEntityConfiguration().getSubject(), identifier.getUri(), e);
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected SignedKeysetContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
+ @Nonnull final SignedKeysetCacheIdentifier identifier, @Nullable final ClassicHttpResponse response,
+ @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+ @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+ if (response != null) {
+ if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
+ log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+ return new SignedKeysetContainer(identifier, null, validExpiration, invalidExpiration);
+ }
+
+ try {
+ final String content = EntityUtils.toString(response.getEntity());
+ log.trace("Attempting to parse signed JWT from content: {}", content);
+ final SignedJWT jwt = SignedJWT.parse(content);
+ if (!JWT_TYPE_HEADER.equals(jwt.getHeader().getType())) {
+ log.warn("Unexpected JWT type header {}", jwt.getHeader().getType());
+ return new SignedKeysetContainer(identifier, null, validExpiration, invalidExpiration);
+ }
+ assert objectMapper != null;
+ final SignedKeyset signedKeyset = SignedKeysetImpl.parse(jwt, objectMapper);
+ return new SignedKeysetContainer(
+ identifier, signedKeyset, validExpiration, invalidExpiration);
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not deserialize signed keyset payload from the response", e);
+ } catch (final ConstraintViolationException e) {
+ log.warn("Parsed signed keyset claims did not pass syntax validation", e);
+ } catch (java.text.ParseException e) {
+ log.warn("Could not parse JWT from the response", e);
+ }
+ return new SignedKeysetContainer(identifier, null, validExpiration, invalidExpiration);
+ } else {
+ log.debug("Unable to fetch signed keyset for: {}, from {} (null response)",
+ identifier.getEntityConfiguration().getSubject(), identifier.getUri());
+ return new SignedKeysetContainer(identifier, null, validExpiration, nullExpiration);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected SignedKeysetContainer handleException(@Nonnull final CriteriaSet criteria,
+ @Nonnull final SignedKeysetCacheIdentifier identifier, @Nonnull final Throwable throwable,
+ @Nonnull final Instant expiration) {
+ return new SignedKeysetContainer(identifier, null, expiration, expiration);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SignedKeysetCacheIdentifier.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SignedKeysetCacheIdentifier.java
new file mode 100644
index 0000000..d8766ce
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SignedKeysetCacheIdentifier.java
@@ -0,0 +1,105 @@
+/*
+ * 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.oidfed.metadata.cache.keyset;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Default identifier for signed keyset within {@link SignedKeysetContainer}.
+ */
+public class SignedKeysetCacheIdentifier implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -4169816153785776493L;
+
+ /** Entity configuration related to the signed keyset. */
+ @Nonnull private final EntityConfiguration entityConfiguration;
+
+ /** URI of the signed keyset. */
+ @Nonnull @NotEmpty private final String uri;
+
+ /**
+ * Constructor.
+ *
+ * @param configuration entity configuration
+ * @param keysetUri keyset URI
+ */
+ public SignedKeysetCacheIdentifier(@Nonnull final EntityConfiguration configuration,
+ @Nonnull @NotEmpty final String keysetUri) {
+ entityConfiguration = Constraint.isNotNull(configuration, "Subject entity configuration cannot be empty");
+ uri = Constraint.isNotEmpty(keysetUri, "Keyset URI cannot be empty");
+ }
+
+ /**
+ * Get the URI of the signed keyset.
+ *
+ * @return uri
+ */
+ @Nonnull @NotEmpty public String getUri() {
+ return uri;
+ }
+
+ /**
+ * Get the subject entity configuration
+ *
+ * @return entity configuration
+ */
+ @Nonnull public EntityConfiguration getEntityConfiguration() {
+ return entityConfiguration;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("entityConfiguration", getEntityConfiguration().getJwt().serialize())
+ .add("uri", getUri())
+ .toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(uri, entityConfiguration);
+ }
+
+ /** {@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 SignedKeysetCacheIdentifier other = (SignedKeysetCacheIdentifier) obj;
+ return uri.equals(other.uri) && entityConfiguration.getJwt().serialize().equals(
+ other.entityConfiguration.getJwt().serialize());
+
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SignedKeysetContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SignedKeysetContainer.java
new file mode 100644
index 0000000..d715b63
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SignedKeysetContainer.java
@@ -0,0 +1,68 @@
+/*
+ * 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.oidfed.metadata.cache.keyset;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.SignedKeyset;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A container class for metadata caches carrying entity ID and keyset URI details related to signed keyset.
+ */
+public class SignedKeysetContainer extends BaseExpirableStatementContainer<SignedKeyset>
+ implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 756269369356865370L;
+
+ /** Signed keyset identifier. */
+ @Nonnull private final SignedKeysetCacheIdentifier identifier;
+
+ /**
+ * Constructor.
+ *
+ * @param id signed keyset identifier, must not be null
+ * @param keyset signed keyset parsed from the response
+ * @param validExpirationInstant expiration instant for valid statement, must not be null
+ * @param invalidExpirationInstant expiration instant for invalid statement, must not be null
+ */
+ public SignedKeysetContainer(@Nonnull final SignedKeysetCacheIdentifier id,
+ @Nullable final SignedKeyset keyset, @Nonnull final Instant validExpirationInstant,
+ @Nonnull final Instant invalidExpirationInstant) {
+ super(validExpirationInstant, keyset, invalidExpirationInstant);
+ identifier = Constraint.isNotNull(id, "Signed keyset identifier cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getEntityId() {
+ return identifier.getEntityConfiguration().getSubject();
+ }
+
+ /**
+ * Get the signed keyset cache identifier.
+ *
+ * @return keyset cache identifier
+ */
+ @Nonnull public SignedKeysetCacheIdentifier getIdentifier() {
+ return identifier;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SubjectSignedKeysetCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SubjectSignedKeysetCriterion.java
new file mode 100644
index 0000000..7d0cfba
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SubjectSignedKeysetCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.cache.keyset;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.SignedKeyset;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a subject signed keyset.
+ */
+public class SubjectSignedKeysetCriterion implements Criterion {
+
+ /** The signed keyset value. */
+ @Nonnull final SignedKeyset value;
+
+ /**
+ * Constructor.
+ *
+ * @param keyset signed keyset value, must not be null
+ */
+ public SubjectSignedKeysetCriterion(@Nonnull final SignedKeyset keyset) {
+ value = Constraint.isNotNull(keyset, "Signed keyset cannot be null");
+ }
+
+ /**
+ * Get the signed keyset value.
+ *
+ * @return the signed keyset value
+ */
+ @Nonnull public SignedKeyset getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "SubjectSignedKeysetCriterion [value=" + value.getJwt().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 SubjectSignedKeysetCriterion other = (SubjectSignedKeysetCriterion) obj;
+ return value.getJwt().serialize().equals(other.value.getJwt().serialize());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SubjectSignedKeysetUriCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SubjectSignedKeysetUriCriterion.java
new file mode 100644
index 0000000..908771e
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/SubjectSignedKeysetUriCriterion.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.oidfed.metadata.cache.keyset;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a signed_jwks_uri value for an entity.
+ */
+public class SubjectSignedKeysetUriCriterion implements Criterion {
+
+ /** The signed keyset URI value. */
+ @Nonnull @NotEmpty private final String value;
+
+ /**
+ * Constructor.
+ *
+ * @param uri the signed keyset URI value, must not be null
+ */
+ public SubjectSignedKeysetUriCriterion(@Nonnull @NotEmpty final String uri) {
+ value = Constraint.isNotEmpty(uri, "Signed keyset URI cannot be null");
+ }
+
+ /**
+ * Get the signed keyset URI value.
+ *
+ * @return the signed keyset URI value
+ */
+ @Nonnull @NotEmpty public String getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "SubjectSignedKeysetUriCriterion [value=" + value + "]";
+ }
+
+ /** {@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 SubjectSignedKeysetUriCriterion other = (SubjectSignedKeysetUriCriterion) obj;
+ return value.equals(other.value);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityConfigurationMetadataSkeletonLookupStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityConfigurationMetadataSkeletonLookupStrategy.java
new file mode 100644
index 0000000..ded0f10
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityConfigurationMetadataSkeletonLookupStrategy.java
@@ -0,0 +1,86 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+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 skeleton for entity configuration metadata via criteria.
+ */
+ at ThreadSafeAfterInit
+public class DefaultEntityConfigurationMetadataSkeletonLookupStrategy extends AbstractIdentifiableInitializableComponent
+ implements Function<CriteriaSet, Map<String,Map<String,Object>>> {
+
+ /** Class logger. */
+ @Nonnull
+ private Logger log = LoggerFactory.getLogger(DefaultEntityConfigurationMetadataSkeletonLookupStrategy.class);
+
+ /** Metadata cache providing metadata skeleton. */
+ @NonnullAfterInit private MetadataCache<Map<String,Map<String,Object>>> metadataSkeletonCache;
+
+ /**
+ * Set the cache providing metadata skeleton.
+ *
+ * @param cache cache providing metadata skeleton
+ */
+ public void setMetadataSkeletonCache(@Nonnull final MetadataCache<Map<String,Map<String,Object>>> cache) {
+ checkSetterPreconditions();
+ metadataSkeletonCache = Constraint.isNotNull(cache, "Metadata skeleton cache cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (metadataSkeletonCache == null) {
+ throw new ComponentInitializationException("Metadata skeleton cache cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public Map<String,Map<String,Object>> apply(@Nullable final CriteriaSet criteria) {
+ checkComponentActive();
+ if (criteria != null) {
+ try {
+ return Optional.of(metadataSkeletonCache.get(criteria))
+ .filter(result -> result.size() == 1)
+ .map(result -> result.get(0))
+ .orElse(CollectionSupport.emptyMap());
+ } catch (final MetadataCacheException e) {
+ log.error("Could not fetch metadata skeleton via metadata cache", e);
+ }
+ }
+ return CollectionSupport.emptyMap();
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityConfigurationResponseFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityConfigurationResponseFetchingStrategy.java
new file mode 100644
index 0000000..d30c918
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityConfigurationResponseFetchingStrategy.java
@@ -0,0 +1,60 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidfed.metadata.cache.ResponseContainerExpirationCriterion;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for fetching entity configuration response container via criteria.
+ */
+ at ThreadSafeAfterInit
+public class DefaultEntityConfigurationResponseFetchingStrategy extends AbstractIdentifiableInitializableComponent
+ implements Function<CriteriaSet, NimbusResponseContainer> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultEntityConfigurationResponseFetchingStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public NimbusResponseContainer apply(@Nullable final CriteriaSet criteria) {
+ checkComponentActive();
+ if (criteria == null) {
+ return null;
+ }
+ final NimbusResponseCriterion responseCriterion = criteria.get(NimbusResponseCriterion.class);
+ if (responseCriterion == null) {
+ log.debug("No response 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;
+ }
+ return new NimbusResponseContainer(responseCriterion.getResponse(), expirationCriterion.getExpirationInstant());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityStatementSignatureValidationFilterStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityStatementSignatureValidationFilterStrategy.java
new file mode 100644
index 0000000..d44faf0
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultEntityStatementSignatureValidationFilterStrategy.java
@@ -0,0 +1,63 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import java.util.function.BiFunction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.trust.TrustEngine;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.cache.AbstractTrustEngineSignatureValidationComponent;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityStatementCriterion;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default signature validating filter for entity statement. The signature validation is performed via configurable
+ * {@link TrustEngine}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultEntityStatementSignatureValidationFilterStrategy
+ extends AbstractTrustEngineSignatureValidationComponent
+ implements BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>> {
+
+ /** Class logger. */
+ @Nonnull private Logger log =
+ LoggerFactory.getLogger(DefaultEntityStatementSignatureValidationFilterStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public EntityStatement<?> apply(@Nullable final EntityStatement<?> entityStatement,
+ @Nullable final MetadataFilterContext filterContext) {
+ checkComponentActive();
+ if (entityStatement == null) {
+ return null;
+ }
+
+ final String entityId = entityStatement.getSubject();
+ log.trace("Starting signature validation of entity statement for {}", entityId);
+ final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(entityStatement));
+ if (validateJwt(entityStatement.getJwt(), criteria, entityId)) {
+ return entityStatement;
+ }
+ return null;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultLocalTrustAnchorsValidator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultLocalTrustAnchorsValidator.java
new file mode 100644
index 0000000..ec56b01
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultLocalTrustAnchorsValidator.java
@@ -0,0 +1,58 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import java.util.Map;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default validator for local trust anchor keyset. It verifies that the keyset only contains public keys. Otherwise
+ * the keyset is considered as invalid.
+ */
+ at ThreadSafe
+public class DefaultLocalTrustAnchorsValidator implements Predicate<Map<String, LocalKeyContainer>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultLocalTrustAnchorsValidator.class);
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final Map<String, LocalKeyContainer> keyContainers) {
+ if (keyContainers == null) {
+ return false;
+ }
+ for (final String anchor : keyContainers.keySet()) {
+ final LocalKeyContainer container = keyContainers.get(anchor);
+ if (container != null) {
+ final JWKSet jwkSet = container.getJWKSet();
+ if (jwkSet != null && jwkSet.containsNonPublicKeys()) {
+ log.warn("Not accepting keyset for {} as it contains non-public keys", anchor);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultNimbusResponseContainerExpirationTimeStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultNimbusResponseContainerExpirationTimeStrategy.java
new file mode 100644
index 0000000..4765f4e
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultNimbusResponseContainerExpirationTimeStrategy.java
@@ -0,0 +1,58 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import com.nimbusds.openid.connect.sdk.federation.config.FederationEntityConfigurationSuccessResponse;
+
+import net.shibboleth.oidc.metadata.cache.ExpirationTimeContext;
+
+/**
+ * Default strategy for fetching expiration time for the Nimbus response container. The expiration instant
+ * is fetched from which is before: the success response message's JWT expiration time or the instant returned by
+ * {@link NimbusResponseContainer#getExpirationInstant()} or {@link ExpirationTimeContext#getMaxCacheDuration()}.
+ */
+ at ThreadSafe
+public class DefaultNimbusResponseContainerExpirationTimeStrategy
+ implements Function<ExpirationTimeContext<NimbusResponseContainer>, Instant> {
+
+ /** {@inheritDoc} */
+ @Nullable public Instant apply(@Nullable final ExpirationTimeContext<NimbusResponseContainer> context) {
+ if (context == null) {
+ return null;
+ }
+ final Instant contextExpiration = context.getNow().plus(context.getMaxCacheDuration());
+ final NimbusResponseContainer container = context.getMetadata();
+ if (container == null) {
+ return contextExpiration;
+ }
+ final List<Instant> expirations = new ArrayList<>(List.of(contextExpiration, container.getExpirationInstant()));
+ if (container.getResponse() instanceof FederationEntityConfigurationSuccessResponse successResponse) {
+ Optional.ofNullable(successResponse.getEntityStatement().getClaimsSet().getExpirationTime())
+ .map(date -> date.toInstant())
+ .ifPresent(instant -> expirations.add(instant));
+ }
+ return Collections.min(expirations);
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultResolveEntityResponseFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultResolveEntityResponseFetchingStrategy.java
new file mode 100644
index 0000000..2bfa1c2
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/DefaultResolveEntityResponseFetchingStrategy.java
@@ -0,0 +1,67 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidfed.metadata.cache.ResponseContainerExpirationCriterion;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * 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.
+ */
+ at ThreadSafeAfterInit
+public class DefaultResolveEntityResponseFetchingStrategy extends AbstractIdentifiableInitializableComponent
+ implements Function<CriteriaSet, ResolveEntityResponseContainer> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultResolveEntityResponseFetchingStrategy.class);
+
+ /** {@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 NimbusResponseCriterion responseCriterion = criteria.get(NimbusResponseCriterion.class);
+ if (responseCriterion == null) {
+ log.debug("No response 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;
+ }
+ return new ResolveEntityResponseContainer(responseCriterion.getResponse(), requestCriterion.getRequest(),
+ expirationCriterion.getExpirationInstant());
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/LocalKeyContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/LocalKeyContainer.java
new file mode 100644
index 0000000..a9de8b8
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/LocalKeyContainer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.oidfed.metadata.jackson.LocalKeyContainerDeserializer;
+
+/**
+ * Key set container class wrapping {@link JWKSet}.
+ */
+ at JsonDeserialize(using = LocalKeyContainerDeserializer.class)
+public class LocalKeyContainer {
+
+ /** The key set. */
+ @Nullable private JWKSet jwkSet;
+
+ /**
+ * Constructor.
+ */
+ public LocalKeyContainer() {
+ }
+
+ /**
+ * Get the key set.
+ *
+ * @return the key set
+ */
+ @Nullable public JWKSet getJWKSet() {
+ return jwkSet;
+ }
+
+ /**
+ * Set the key set.
+ * @param set key set
+ */
+ public void setJWKSet(@Nullable final JWKSet set) {
+ jwkSet = set;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/NimbusResponseContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/NimbusResponseContainer.java
new file mode 100644
index 0000000..da9d200
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/NimbusResponseContainer.java
@@ -0,0 +1,71 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.nimbusds.oauth2.sdk.Response;
+
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A container class for metadata caches carrying Nimbus response message.
+ */
+public class NimbusResponseContainer implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 656269369356865370L;
+
+ /** Response message. */
+ @Nullable private final Response response;
+
+ /** Expiration instant for this container. */
+ @Nonnull private final Instant expiration;
+
+ /**
+ * Constructor.
+ *
+ * @param responseMessage response message
+ * @param expirationInstant expiration instant
+ */
+ public NimbusResponseContainer(@Nullable final Response responseMessage,
+ @Nonnull final Instant expirationInstant) {
+ response = responseMessage;
+ expiration = Constraint.isNotNull(expirationInstant, "Expiration instant cannot be null");
+ }
+
+ /**
+ * Get response message.
+ *
+ * @return response message
+ */
+ @Nullable public Response getResponse() {
+ return response;
+ }
+
+ /**
+ * Get expiration instant.
+ *
+ * @return expiration instant
+ */
+ @Nonnull public Instant getExpirationInstant() {
+ return expiration;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/NimbusResponseCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/NimbusResponseCriterion.java
new file mode 100644
index 0000000..8349be9
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/NimbusResponseCriterion.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.oidfed.metadata.cache.local;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.oauth2.sdk.Response;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} carrying Nimbus response message.
+ */
+public class NimbusResponseCriterion implements Criterion {
+
+ /** The response message. */
+ @Nonnull private final Response response;
+
+ /**
+ * Constructor.
+ *
+ * @param responseMessage response message, must not be null
+ */
+ public NimbusResponseCriterion(@Nonnull final Response responseMessage) {
+ response = Constraint.isNotNull(responseMessage, "Response cannot be null");
+ }
+
+ /**
+ * Get the response message.
+ *
+ * @return the response message
+ */
+ @Nonnull public Response getResponse() {
+ return response;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "NimbusResponseCriterion [response=" + response + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(response);
+ }
+
+ /** {@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 NimbusResponseCriterion other = (NimbusResponseCriterion) obj;
+ return response.equals(other.response);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/ResolveEntityRequestCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/ResolveEntityRequestCriterion.java
new file mode 100644
index 0000000..d6a249f
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/ResolveEntityRequestCriterion.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.oidfed.metadata.cache.local;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.messaging.impl.ResolveEntityRequest;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing request message to a resolve entity API.
+ */
+public class ResolveEntityRequestCriterion implements Criterion {
+
+ /** The request message. */
+ @Nonnull private final ResolveEntityRequest request;
+
+ /**
+ * Constructor.
+ *
+ * @param requestMessage request message, must not be null
+ */
+ public ResolveEntityRequestCriterion(@Nonnull final ResolveEntityRequest requestMessage) {
+ request = Constraint.isNotNull(requestMessage, "Request cannot be null");
+ }
+
+ /**
+ * Get the request message.
+ *
+ * @return the request message
+ */
+ @Nonnull public ResolveEntityRequest getRequest() {
+ return request;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "ResolveEntityRequestCriterion [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 ResolveEntityRequestCriterion other = (ResolveEntityRequestCriterion) obj;
+ //TODO: proper equals-check
+ return request.equals(other.request);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/ResolveEntityResponseContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/ResolveEntityResponseContainer.java
new file mode 100644
index 0000000..519d687
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/local/ResolveEntityResponseContainer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.oidfed.metadata.cache.local;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.nimbusds.oauth2.sdk.Response;
+
+import net.shibboleth.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.
+ */
+public class ResolveEntityResponseContainer extends NimbusResponseContainer implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 756269369356865370L;
+
+ /** Request message. */
+ @Nonnull private final ResolveEntityRequest request;
+
+ /**
+ * Constructor.
+ *
+ * @param responseMessage response message
+ * @param requestMessage request message
+ * @param expirationInstant expiration instant
+ */
+ 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");
+ }
+
+ /**
+ * Get request message.
+ *
+ * @return request message
+ */
+ public ResolveEntityRequest getRequest() {
+ return request;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java
new file mode 100644
index 0000000..492976c
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java
@@ -0,0 +1,162 @@
+/*
+ * 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.oidfed.metadata.cache.resolver;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+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.HttpRequest;
+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.apache.hc.core5.net.URIBuilder;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.ResolveEntityResponse;
+import net.shibboleth.oidfed.metadata.cache.AbstractFederationEndpointResponseFetchingStrategy;
+import net.shibboleth.oidfed.metadata.impl.ResolveEntityResponseImpl;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for fetching trust chain data via resolve entity API for a request specified in the criteria set.
+ */
+ at ThreadSafeAfterInit
+public class DefaultResolveEntityTrustChainFetchingStrategy
+ extends AbstractFederationEndpointResponseFetchingStrategy
+ <ResolveEntityCacheContainerIdentifier, ResolveEntityResponseContainer> {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("resolve-response+jwt");
+
+ /** The HTTP response content type. */
+ @Nonnull public static final String HTTP_RESPONSE_CONTENT_TYPE = "application/" + JWT_TYPE_HEADER.toString();
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultResolveEntityTrustChainFetchingStrategy.class);
+
+ /**
+ * Constructor.
+ */
+ public DefaultResolveEntityTrustChainFetchingStrategy() {
+ setCriteriaToRequestDataStrategy(criteria -> {
+ final ResolveEntityCacheIdentifierCriterion identifierCriterion =
+ criteria.get(ResolveEntityCacheIdentifierCriterion.class);
+ if (identifierCriterion == null) {
+ log.debug("No identifier criterion given, returning null");
+ return null;
+ }
+ return identifierCriterion.getIdentifier();
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+ @Nonnull ResolveEntityCacheContainerIdentifier identifier) {
+
+ final List<NameValuePair> nvps = new ArrayList<>();
+ nvps.add(new BasicNameValuePair("sub", identifier.getSubject()));
+ identifier.getTrustAnchors().forEach(anchor -> nvps.add(new BasicNameValuePair("trust_anchor", anchor)));
+ identifier.getEntityTypes().forEach(type -> nvps.add(new BasicNameValuePair("entity_type", type)));
+
+ final BiConsumer<HttpRequest,List<NameValuePair>> clientAuthentication = getClientAuthentication(criteria);
+ if (clientAuthentication == null) {
+ final HttpGet httpRequest = new HttpGet(identifier.getEndpoint());
+ try {
+ final URI uri = new URIBuilder(httpRequest.getUri()).addParameters(nvps).build();
+ httpRequest.setUri(uri);
+ } catch (final URISyntaxException e) {
+ log.error("Could not create URI with the given parameters {}", identifier, e);
+ }
+ return httpRequest;
+ } else {
+ final HttpPost httpRequest = new HttpPost(identifier.getEndpoint());
+ clientAuthentication.accept(httpRequest, nvps);
+ 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 ResolveEntityResponseContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
+ @Nonnull final ResolveEntityCacheContainerIdentifier identifier,
+ @Nullable final ClassicHttpResponse response, @Nonnull final Instant validExpiration,
+ @Nonnull final Instant invalidExpiration, @Nonnull final Instant nullExpiration)
+ throws ProtocolException, IOException {
+ if (response != null) {
+ if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
+ log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+ return new ResolveEntityResponseContainer(identifier, null, validExpiration, invalidExpiration);
+ }
+
+ try {
+ final SignedJWT jwt = SignedJWT.parse(EntityUtils.toString(response.getEntity()));
+ if (!JWT_TYPE_HEADER.equals(jwt.getHeader().getType())) {
+ log.warn("Unexpected JWT type header {}", jwt.getHeader().getType());
+ return new ResolveEntityResponseContainer(identifier, null, validExpiration,
+ invalidExpiration);
+ }
+ assert objectMapper != null;
+ final ResolveEntityResponse resolveEntity = ResolveEntityResponseImpl.parse(jwt, objectMapper);
+ return new ResolveEntityResponseContainer(
+ identifier, resolveEntity, validExpiration, invalidExpiration);
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not deserialize entity statement payload from the response", e);
+ } catch (final ConstraintViolationException e) {
+ log.warn("Parsed entity configuration claims did not pass syntax validation", e);
+ } catch (java.text.ParseException e) {
+ log.warn("Could not parse JWT from the response", e);
+ }
+ return new ResolveEntityResponseContainer(identifier, null, validExpiration, invalidExpiration);
+ } else {
+ log.debug("Unable to fetch resolve entity response: {} (null response)", identifier.getEndpoint());
+ return new ResolveEntityResponseContainer(identifier, null, validExpiration, nullExpiration);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected ResolveEntityResponseContainer handleException(@Nonnull final CriteriaSet criteria,
+ @Nonnull final ResolveEntityCacheContainerIdentifier identifier, @Nonnull final Throwable exception,
+ @Nonnull final Instant expiration) {
+ return new ResolveEntityResponseContainer(identifier, null, expiration, expiration);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityCacheContainerIdentifier.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityCacheContainerIdentifier.java
new file mode 100644
index 0000000..f27116f
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityCacheContainerIdentifier.java
@@ -0,0 +1,143 @@
+/*
+ * 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.oidfed.metadata.cache.resolver;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Default identifier for trust mark within {@link ResolveEntityResponseContainer}.
+ */
+public class ResolveEntityCacheContainerIdentifier implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -4143516153785776493L;
+
+ /** The endpoint URI of the request. */
+ @Nonnull @NotEmpty private final String endpoint;
+
+ /** The requested subject. */
+ @Nonnull @NotEmpty private final String subject;
+
+ /** The requested trust anchors. */
+ @Nonnull @NotEmpty private final List<String> trustAnchors;
+
+ /** The requested entity types to resolve. */
+ @Nonnull private final List<String> entityTypes;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param uri endpoint URI
+ * @param sub subject
+ * @param anchors trust anchors
+ * @param types optional entity types
+ */
+ public ResolveEntityCacheContainerIdentifier(@Nonnull final String uri,
+ @Nonnull @NotEmpty final String sub,
+ @Nonnull @NotEmpty final List<String> anchors,
+ @Nullable final List<String> types) {
+ endpoint = Constraint.isNotEmpty(uri, "Endpoint URI cannot be null");
+ subject = Constraint.isNotNull(sub, "Subject cannot be empty");
+ Constraint.isNotEmpty(anchors, "Trust anchors cannot be empty");
+ trustAnchors = anchors;
+ entityTypes = types == null ? CollectionSupport.emptyList() : CollectionSupport.copyToList(types);
+ }
+
+ /**
+ * Returns the requested subject.
+ *
+ * @return The subject.
+ */
+ @Nonnull @NotEmpty public String getSubject() {
+ return subject;
+ }
+
+ /**
+ * Returns the requested trust anchor.
+ *
+ * @return The trust anchor.
+ */
+ @Nonnull @NotEmpty public List<String> getTrustAnchors() {
+ return trustAnchors;
+ }
+
+ /**
+ * Returns the requested entity type to resolve.
+ *
+ * @return The entity type to resolve.
+ */
+ @Nonnull public List<String> getEntityTypes() {
+ return entityTypes;
+ }
+
+ /**
+ * Get the endpoint for the request.
+ *
+ * @return endpoint
+ */
+ @Nonnull @NotEmpty public String getEndpoint() {
+ return endpoint;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("subject", getSubject())
+ .add("trustAnchors", getTrustAnchors())
+ .add("entityTypes", getEntityTypes())
+ .add("endpoint", getEndpoint())
+ .toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(endpoint, subject, trustAnchors, entityTypes);
+ }
+
+ /** {@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 ResolveEntityCacheContainerIdentifier other = (ResolveEntityCacheContainerIdentifier) obj;
+ return endpoint.equals(other.endpoint) && subject.equals(other.subject)
+ && trustAnchors.containsAll(other.trustAnchors) && other.trustAnchors.containsAll(trustAnchors)
+ && entityTypes.containsAll(other.entityTypes) && other.entityTypes.containsAll(entityTypes);
+
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityCacheIdentifierCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityCacheIdentifierCriterion.java
new file mode 100644
index 0000000..511e7a1
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityCacheIdentifierCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.cache.resolver;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing request message details to Resolve Entity API.
+ */
+public class ResolveEntityCacheIdentifierCriterion implements Criterion {
+
+ /** The trust mark identifier. */
+ @Nonnull private final ResolveEntityCacheContainerIdentifier identifier;
+
+ /**
+ * Constructor.
+ *
+ * @param id identifier, must not be null
+ */
+ public ResolveEntityCacheIdentifierCriterion(@Nonnull final ResolveEntityCacheContainerIdentifier id) {
+ identifier = Constraint.isNotNull(id, "Identifier cannot be null");
+ }
+
+ /**
+ * Get the trust mark identifier.
+ *
+ * @return the identifier
+ */
+ @Nonnull public ResolveEntityCacheContainerIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "RemoteEntityCacheIdentifierCriterion [identifier=" + identifier + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(identifier);
+ }
+
+ /** {@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 ResolveEntityCacheIdentifierCriterion other = (ResolveEntityCacheIdentifierCriterion) obj;
+ return identifier.equals(other.identifier);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityResponseContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityResponseContainer.java
new file mode 100644
index 0000000..07df79b
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/ResolveEntityResponseContainer.java
@@ -0,0 +1,69 @@
+/*
+ * 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.oidfed.metadata.cache.resolver;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.ResolveEntityResponse;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A container class for metadata caches carrying ID and statement details related to Resolve Entity.
+ */
+public class ResolveEntityResponseContainer extends BaseExpirableStatementContainer<ResolveEntityResponse>
+ implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 756269564456865370L;
+
+ /** Requested entity ID. */
+ @Nonnull private final ResolveEntityCacheContainerIdentifier identifier;
+
+ /**
+ * Constructor.
+ *
+ * @param id identifier, must not be null
+ * @param resolveEntity resolve entity response parsed from the response
+ * @param validExpirationInstant expiration instant for valid statement, must not be null
+ * @param invalidExpirationInstant expiration instant for invalid statement, must not be null
+ */
+ public ResolveEntityResponseContainer(@Nonnull final ResolveEntityCacheContainerIdentifier id,
+ @Nullable final ResolveEntityResponse resolveEntity, @Nonnull final Instant validExpirationInstant,
+ @Nonnull final Instant invalidExpirationInstant) {
+ super(validExpirationInstant, resolveEntity, invalidExpirationInstant);
+ identifier = Constraint.isNotNull(id, "Identifier cannot be null");
+ }
+
+ /**
+ * Get entity ID.
+ *
+ * @return entity ID
+ */
+ @Nonnull @NotEmpty public ResolveEntityCacheContainerIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getEntityId() {
+ return identifier.getSubject();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementCriteriaToIdentifierStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementCriteriaToIdentifierStrategy.java
new file mode 100644
index 0000000..7b2054a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementCriteriaToIdentifierStrategy.java
@@ -0,0 +1,93 @@
+/*
+ * 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.oidfed.metadata.cache.subordinate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.resolver.CriteriaSet;
+
+/**
+ * Default identifier strategy for subordinate statement. The criteria set is transformed into a
+ * {@link SubordinateStatementCacheIdentifier} that consists of subject and issuer entity IDs.
+ */
+ at ThreadSafeAfterInit
+public class DefaultSubordinateStatementCriteriaToIdentifierStrategy
+ extends AbstractIdentifiableInitializableComponent
+ implements Function<CriteriaSet, SubordinateStatementCacheIdentifier> {
+
+ /** Strategy to fetch subject entity ID from criteria set. */
+ @NonnullAfterInit private Function<CriteriaSet, String> criteriaToSubjectEntityIdStrategy;
+
+ /** Strategy to fetch issuer entity ID from criteria set. */
+ @NonnullAfterInit private Function<CriteriaSet, String> criteriaToIssuerEntityIdStrategy;
+
+ /**
+ * Set the strategy to fetch subject entity ID from criteria set.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setCriteriaToSubjectEntityIdStrategy(@Nonnull final Function<CriteriaSet, String> strategy) {
+ checkSetterPreconditions();
+ criteriaToSubjectEntityIdStrategy = Constraint.isNotNull(strategy,
+ "Criteria to subject entity ID strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy to fetch issuer entity ID from criteria set.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setCriteriaToIssuerEntityIdStrategy(@Nonnull final Function<CriteriaSet, String> strategy) {
+ checkSetterPreconditions();
+ criteriaToIssuerEntityIdStrategy = Constraint.isNotNull(strategy,
+ "Criteria to issuer entity ID strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (criteriaToSubjectEntityIdStrategy == null) {
+ throw new ComponentInitializationException("Criteria to subject entity ID strategy cannot be null");
+ }
+ if (criteriaToIssuerEntityIdStrategy == null) {
+ throw new ComponentInitializationException("Criteria to issuer entity ID strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public SubordinateStatementCacheIdentifier apply(@Nullable final CriteriaSet criteria) {
+ checkComponentActive();
+ final String subjectEntityId = criteriaToSubjectEntityIdStrategy.apply(criteria);
+ if (subjectEntityId == null) {
+ return null;
+ }
+ final String issuerEntityId = criteriaToIssuerEntityIdStrategy.apply(criteria);
+ if (issuerEntityId == null) {
+ return null;
+ }
+ return new SubordinateStatementCacheIdentifier(issuerEntityId, subjectEntityId);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java
new file mode 100644
index 0000000..3b7c9db
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java
@@ -0,0 +1,236 @@
+/*
+ * 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.oidfed.metadata.cache.subordinate;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+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.HttpRequest;
+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.apache.hc.core5.net.URIBuilder;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.cache.AbstractFederationEndpointResponseFetchingStrategy;
+import net.shibboleth.oidfed.metadata.cache.FederationEndpointEntityStatementCriterion;
+import net.shibboleth.oidfed.metadata.cache.IssuerEntityIDCriterion;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityIDCriterion;
+import net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer;
+import net.shibboleth.oidfed.metadata.impl.SubordinateStatementImpl;
+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.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for fetching entity configuration for the request specified in the criteria set. The parsed
+ * response details are stored inside a {@link EntityConfigurationContainer}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultSubordinateStatementFetchingStrategy
+ extends AbstractFederationEndpointResponseFetchingStrategy
+ <SubordinateStatementCacheIdentifier, SubordinateStatementContainer> {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("entity-statement+jwt");
+
+ /** The HTTP response content type. */
+ @Nonnull public static final String HTTP_RESPONSE_CONTENT_TYPE = "application/" + JWT_TYPE_HEADER.toString();
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultSubordinateStatementFetchingStrategy.class);
+
+ /** The entity configuration cache used for fetching issuer statement. */
+ @NonnullAfterInit private MetadataCache<EntityConfigurationContainer> entityConfigurationCache;
+
+ /**
+ * Constructor.
+ */
+ public DefaultSubordinateStatementFetchingStrategy() {
+ setCriteriaToRequestDataStrategy(criteria -> {
+ final SubjectEntityIDCriterion subjectCriterion = criteria.get(SubjectEntityIDCriterion.class);
+ if (subjectCriterion == null) {
+ log.debug("No SubjectEntityIDCriterion, returning null");
+ return null;
+ }
+ final IssuerEntityIDCriterion issuerCriterion = criteria.get(IssuerEntityIDCriterion.class);
+ if (issuerCriterion == null) {
+ log.debug("No IssuerEntityIDCriterion, returning null");
+ return null;
+ }
+ return new SubordinateStatementCacheIdentifier(issuerCriterion.getValue(), subjectCriterion.getValue());
+ });
+ }
+
+ /**
+ * Set the entity configuration cache used for fetching issuer statement.
+ *
+ * @param cache entity configuration cache
+ */
+ public void setEntityConfigurationCache(@Nonnull final MetadataCache<EntityConfigurationContainer> cache) {
+ checkSetterPreconditions();
+ entityConfigurationCache = Constraint.isNotNull(cache, "Entity configuration cache cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (entityConfigurationCache == null) {
+ throw new ComponentInitializationException("Entity configuration cache cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+ @Nonnull final SubordinateStatementCacheIdentifier id) {
+ final String issuerEntityId = id.getIssuer();
+ final List<EntityConfigurationContainer> issuerStatements;
+ try {
+ issuerStatements =
+ entityConfigurationCache.get(new CriteriaSet(new SubjectEntityIDCriterion(issuerEntityId)));
+ } catch (final MetadataCacheException e) {
+ log.error("Could not resolve entity configuration for issuer {}", issuerEntityId, e);
+ return null;
+ }
+ if (issuerStatements.isEmpty()) {
+ log.error("Could not resolve entity configuration for issuer {}", issuerEntityId);
+ return null;
+ }
+ final EntityStatement<?> issuerStatement =
+ Optional.ofNullable(issuerStatements.get(0).getStatement())
+ .filter(statement -> statement instanceof EntityStatement<?>)
+ .map(statement -> (EntityStatement<?>) statement)
+ .orElse(null);
+ if (issuerStatement == null) {
+ log.warn("Could not resolve entity configuration for issuer {}", issuerEntityId);
+ return null;
+ }
+ final Map<String, Object> federationEntity =
+ Optional.of(issuerStatement)
+ .map(statement -> statement.getParsedPayload().getMetadata())
+ .map(metadata -> metadata.getFederationEntityMetadata())
+ .orElse(null);
+ if (federationEntity == null) {
+ log.warn("Could not resolve federation entity metadata for issuer {}", issuerEntityId);
+ return null;
+ }
+ if (federationEntity.get("federation_fetch_endpoint") instanceof String endpoint) {
+ final BiConsumer<HttpRequest, List<NameValuePair>> clientAuthentication =
+ getClientAuthentication(new CriteriaSet(
+ new FederationEndpointEntityStatementCriterion(issuerStatement)));
+ if (clientAuthentication == null) {
+ final HttpGet httpRequest = new HttpGet(endpoint);
+ try {
+ final URI uri =
+ new URIBuilder(httpRequest.getUri()).addParameter("sub", id.getSubject()).build();
+ httpRequest.setUri(uri);
+ } catch (final URISyntaxException e) {
+ log.error("Could not create URI with the given parameters {}", issuerEntityId, e);
+ }
+ return httpRequest;
+ } else {
+ final HttpPost httpRequest = new HttpPost(endpoint);
+ final List<NameValuePair> parameters = new ArrayList<>(
+ List.of(new BasicNameValuePair("sub", id.getSubject())));
+ clientAuthentication.accept(httpRequest, parameters);
+ final UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(parameters,
+ StandardCharsets.UTF_8);
+ httpRequest.setEntity(urlEncodedFormEntity);
+ httpRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED);
+ return httpRequest;
+ }
+ } else {
+ log.warn("Could not parse federation_fetch_endpoint for {}", issuerEntityId);
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected SubordinateStatementContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
+ @Nonnull final SubordinateStatementCacheIdentifier id, @Nullable final ClassicHttpResponse response,
+ @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+ @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+ if (response != null) {
+ if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
+ log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+ return new SubordinateStatementContainer(id, null, validExpiration, invalidExpiration);
+ }
+
+ try {
+ final String content = EntityUtils.toString(response.getEntity());
+ log.trace("Attempting to parse signed JWT from content: {}", content);
+ final SignedJWT jwt = SignedJWT.parse(content);
+ if (!JWT_TYPE_HEADER.equals(jwt.getHeader().getType())) {
+ log.warn("Unexpected JWT type header {}", jwt.getHeader().getType());
+ return new SubordinateStatementContainer(id, null, validExpiration, invalidExpiration);
+ }
+ assert objectMapper != null;
+ final SubordinateStatement statement = SubordinateStatementImpl.parse(jwt, objectMapper);
+ log.trace("Subordinate statement successfully parsed and stored into the container");
+ return new SubordinateStatementContainer(id, statement, validExpiration, invalidExpiration);
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not deserialize entity statement payload from the response", e);
+ } catch (final ConstraintViolationException e) {
+ log.warn("Parsed entity configuration claims did not pass syntax validation", e);
+ } catch (java.text.ParseException e) {
+ log.warn("Could not parse JWT from the response", e);
+ }
+ return new SubordinateStatementContainer(id, null, validExpiration, invalidExpiration);
+ } else {
+ log.debug("Unable to fetch entity configuration for: {} (null response)", id);
+ return new SubordinateStatementContainer(id, null, validExpiration, nullExpiration);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected SubordinateStatementContainer handleException(@Nonnull final CriteriaSet criteria,
+ @Nonnull final SubordinateStatementCacheIdentifier id, @Nonnull final Throwable exception,
+ @Nonnull final Instant expiration) {
+ return new SubordinateStatementContainer(id, null, expiration, expiration);
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/SubordinateStatementCacheIdentifier.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/SubordinateStatementCacheIdentifier.java
new file mode 100644
index 0000000..0315576
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/SubordinateStatementCacheIdentifier.java
@@ -0,0 +1,103 @@
+/*
+ * 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.oidfed.metadata.cache.subordinate;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Default identifier for subordinate statement within {@link SubordinateStatementContainer}.
+ */
+public class SubordinateStatementCacheIdentifier implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -4169816153785776493L;
+
+ /** Issuer of the subordinate statement. */
+ @Nonnull @NotEmpty private final String issuer;
+
+ /** Subject of the subordinate statement. */
+ @Nonnull @NotEmpty private final String subject;
+
+ /**
+ * Constructor.
+ *
+ * @param iss issuer
+ * @param sub subject
+ */
+ public SubordinateStatementCacheIdentifier(@Nonnull @NotEmpty final String iss,
+ @Nonnull @NotEmpty final String sub) {
+ issuer = Constraint.isNotEmpty(iss, "Issuer cannot be empty");
+ subject = Constraint.isNotEmpty(sub, "Subject cannot be empty");
+ }
+
+ /**
+ * Get the issuer of the subordinate statement.
+ *
+ * @return issuer
+ */
+ @Nonnull @NotEmpty public String getIssuer() {
+ return issuer;
+ }
+
+ /**
+ * Get the subject of the subordinate statement.
+ *
+ * @return subject
+ */
+ @Nonnull @NotEmpty public String getSubject() {
+ return subject;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("issuer", getIssuer())
+ .add("subject", getSubject())
+ .toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(issuer, subject);
+ }
+
+ /** {@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 SubordinateStatementCacheIdentifier other = (SubordinateStatementCacheIdentifier) obj;
+ return issuer.equals(other.issuer) && subject.equals(other.subject);
+
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/SubordinateStatementContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/SubordinateStatementContainer.java
new file mode 100644
index 0000000..b526d6a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/SubordinateStatementContainer.java
@@ -0,0 +1,69 @@
+/*
+ * 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.oidfed.metadata.cache.subordinate;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A container class for metadata caches carrying ID and statement details related to Subordinate Statement.
+ */
+public class SubordinateStatementContainer extends BaseExpirableStatementContainer<SubordinateStatement>
+ implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 756269369356865370L;
+
+ /** Requested entity ID. */
+ @Nonnull private final SubordinateStatementCacheIdentifier identifier;
+
+ /**
+ * Constructor.
+ *
+ * @param id identifier, must not be null
+ * @param statement subordinate statement parsed from the response
+ * @param validExpirationInstant expiration instant for valid statement, must not be null
+ * @param invalidExpirationInstant expiration instant for invalid statement, must not be null
+ */
+ public SubordinateStatementContainer(@Nonnull final SubordinateStatementCacheIdentifier id,
+ @Nullable final SubordinateStatement statement, @Nonnull final Instant validExpirationInstant,
+ @Nonnull final Instant invalidExpirationInstant) {
+ super(validExpirationInstant, statement, invalidExpirationInstant);
+ identifier = Constraint.isNotNull(id, "Identifier cannot be null");
+ }
+
+ /**
+ * Get entity ID.
+ *
+ * @return entity ID
+ */
+ @Nonnull @NotEmpty public SubordinateStatementCacheIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getEntityId() {
+ return identifier.getSubject();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategy.java
new file mode 100644
index 0000000..a6069b4
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategy.java
@@ -0,0 +1,463 @@
+/*
+ * 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.oidfed.metadata.cache.trustchain;
+
+import java.time.Duration;
+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.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.cache.IssuerEntityIDCriterion;
+import net.shibboleth.oidfed.metadata.cache.PreSelectedTrustChainCriterion;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityIDCriterion;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityStatementCriterion;
+import net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer;
+import net.shibboleth.oidfed.metadata.cache.local.LocalKeyContainer;
+import net.shibboleth.oidfed.metadata.cache.subordinate.SubordinateStatementContainer;
+import net.shibboleth.oidfed.metadata.util.EntityStatementHelper;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for fetching trust chains 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.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableInitializableComponent
+ implements Function<CriteriaSet, TrustChainsContainer> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainFetchingStrategy.class);
+
+ /** Strategy for fetching entity ID from the criteria set. */
+ @NonnullAfterInit private Function<CriteriaSet, String> criteriaToSubjectEntityIdStrategy;
+
+ /** Cache for entity configurations. */
+ @NonnullAfterInit private MetadataCache<EntityConfigurationContainer> entityConfigurationCache;
+
+ /** Cache for subordinate entity statements. */
+ @NonnullAfterInit private MetadataCache<SubordinateStatementContainer> subordinateStatementCache;
+
+ /** Cache containing local copies of trusted trust anchor keys. */
+ @NonnullAfterInit private MetadataCache<Map<String, LocalKeyContainer>> localTrustAnchorsCache;
+
+ /** Strategy to fetch lifetime for container with valid contents. */
+ @NonnullAfterInit private Function<CriteriaSet, Duration> criteriaToValidContainerLifetimeStrategy;
+
+ /** Strategy to fetch lifetime for container with invalid contents. */
+ @NonnullAfterInit private Function<CriteriaSet, Duration> criteriaToInvalidContainerLifetimeStrategy;
+
+ /**
+ * Set the strategy for fetching entity ID from the criteria set.
+ *
+ * @param strategy fetching strategy
+ */
+ public void setCriteriaToSubjectEntityIdStrategy(@Nonnull final Function<CriteriaSet, String> strategy) {
+ checkSetterPreconditions();
+ criteriaToSubjectEntityIdStrategy = Constraint.isNotNull(strategy,
+ "Criteria to subject entity ID strategy cannot be null");
+ }
+
+ /**
+ * Set the cache for entity configurations.
+ *
+ * @param cache entity configurations cache
+ */
+ public void setEntityConfigurationCache(@Nonnull final MetadataCache<EntityConfigurationContainer> cache) {
+ checkSetterPreconditions();
+ entityConfigurationCache = Constraint.isNotNull(cache, "Entity Configuration cache cannot be null");
+ }
+
+ /**
+ * Set the cache for subordinate statement.
+ *
+ * @param cache subordinate statements cache.
+ */
+ public void setSubordinateStatementCache(@Nonnull final MetadataCache<SubordinateStatementContainer> cache) {
+ checkSetterPreconditions();
+ subordinateStatementCache = Constraint.isNotNull(cache, "Subordinate statement cache cannot be null");
+ }
+
+ /**
+ * 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 strategy to fetch lifetime for container with valid contents.
+ *
+ * @param strategy lifetime strategy
+ */
+ public void setCriteriaToValidContainerLifetimeStrategy(@Nonnull final Function<CriteriaSet, Duration> strategy) {
+ checkSetterPreconditions();
+
+ criteriaToValidContainerLifetimeStrategy =
+ Constraint.isNotNull(strategy, "Criteria to valid container lifetime strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy to fetch lifetime for container with invalid contents.
+ *
+ * @param strategy lifetime strategy
+ */
+ public void setCriteriaToInvalidContainerLifetimeStrategy(
+ @Nonnull final Function<CriteriaSet, Duration> strategy) {
+ checkSetterPreconditions();
+
+ criteriaToInvalidContainerLifetimeStrategy =
+ Constraint.isNotNull(strategy, "Criteria to invalid container lifetime strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (criteriaToSubjectEntityIdStrategy == null) {
+ throw new ComponentInitializationException("Criteria to subject entity ID strategy cannot be null");
+ }
+ if (entityConfigurationCache == null) {
+ throw new ComponentInitializationException("Entity configuration cache cannot be null");
+ }
+ if (subordinateStatementCache == null) {
+ throw new ComponentInitializationException("Subordinate statement cache cannot be null");
+ }
+ if (localTrustAnchorsCache == null) {
+ throw new ComponentInitializationException("Local Trust Anchor cache cannot be null");
+ }
+ if (criteriaToValidContainerLifetimeStrategy == null) {
+ throw new ComponentInitializationException("Criteria to valid container lifetime strategy cannot be null");
+ }
+ if (criteriaToInvalidContainerLifetimeStrategy == null) {
+ throw new ComponentInitializationException(
+ "Criteria to invalid container lifetime strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public TrustChainsContainer apply(@Nullable final CriteriaSet criteria) {
+ checkComponentActive();
+ if (criteria == null) {
+ return null;
+ }
+ final Duration validLifetime = criteriaToValidContainerLifetimeStrategy.apply(criteria);
+ if (validLifetime == null) {
+ log.warn("Could not fetch expiration time for valid container");
+ return null;
+ }
+ final Instant validExpiration = Instant.now().plus(validLifetime);
+ final Duration invalidLifetime = criteriaToInvalidContainerLifetimeStrategy.apply(criteria);
+ if (invalidLifetime == null) {
+ log.warn("Could not fetch expiration time for invalid container");
+ return null;
+ }
+ final Instant invalidExpiration = Instant.now().plus(invalidLifetime);
+ final PreSelectedTrustChainCriterion preSelectedCriterion = criteria.get(PreSelectedTrustChainCriterion.class);
+ final List<String> preSelectedChain =
+ preSelectedCriterion == null ? CollectionSupport.emptyList() : preSelectedCriterion.getValue();
+ final EntityConfiguration entityConfiguration;
+ final SubjectEntityStatementCriterion subjectStatementCriterion =
+ criteria.get(SubjectEntityStatementCriterion.class);
+ if (subjectStatementCriterion == null) {
+ try {
+ final List<EntityConfigurationContainer> containers = entityConfigurationCache.get(criteria);
+ if (containers.isEmpty()) {
+ return null;
+ }
+ entityConfiguration = containers.get(0).getStatement();
+ } catch (final MetadataCacheException e) {
+ log.error("Could not fetch entity configuration for the trust chain", e);
+ return null;
+ }
+ } else {
+ entityConfiguration = subjectStatementCriterion.getValue() instanceof EntityConfiguration ec ? ec : null;
+ }
+
+ log.trace("Entity configuration found to build the trust chains on: {}", entityConfiguration != null);
+ if (entityConfiguration == null) {
+ return null;
+ }
+
+ final TrustChainWrapper initialWrapper =
+ new TrustChainWrapper(CollectionSupport.listOf(entityConfiguration));
+ if (initialWrapper.isComplete()) {
+ log.warn("Entity configuration for {} does not contain authority hints, cannot resolve trust chains",
+ entityConfiguration.getSubject());
+ return null;
+ }
+ final List<TrustChainWrapper> populatedWrappers = populateChain(
+ CollectionSupport.listOf(initialWrapper), preSelectedChain);
+ final List<List<EntityStatement<?>>> result = populatedWrappers.stream()
+ .filter(wrapper -> wrapper.isComplete())
+ .map(wrapper -> wrapper.getTrustChain())
+ .toList();
+ assert result != null; assert invalidExpiration != null; assert validExpiration != null;
+ return new TrustChainsContainer(result.isEmpty() ? invalidExpiration : validExpiration,
+ entityConfiguration.getSubject(), result);
+ }
+
+ /**
+ * Checks whether the given entity is locally trusted via local trust anchors cache.
+ *
+ * @param statement the entity to be verified
+ * @return true if the entity is locally trusted, false otherwise
+ */
+ private boolean isLocallyTrusted(@Nonnull final EntityStatement<?> statement) {
+ final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(statement));
+ final String entityId = statement.getSubject();
+ assert entityId != null;
+ final List<Map<String, LocalKeyContainer>> keyContainers;
+ try {
+ keyContainers = localTrustAnchorsCache.get(criteria);
+ } catch (final MetadataCacheException e) {
+ log.debug("Could not resolve local trust anchor keys from the cache for {}", entityId, e);
+ return false;
+ }
+ if (keyContainers.isEmpty() || !keyContainers.get(0).containsKey(entityId)) {
+ log.trace("No locally trusted keys found for {}", entityId);
+ return false;
+ }
+ log.trace("Locally trusted keys configured for {}", entityId);
+ return true;
+ }
+
+ /**
+ * Recursively populates the given list of trust chains until the last entity statement in each chain doesn't
+ * contain any authority hints.
+ *
+ * @param entities the list of trust chains being populated.
+ * @param preSelectedChain the pre-selected trust chain (containing entity IDs as String).
+ * @return the list of trust chains being populated
+ */
+ @Nonnull @NonnullElements private List<TrustChainWrapper> populateChain(
+ @Nonnull @NonnullElements final List<TrustChainWrapper> entities,
+ @Nonnull final List<String> preSelectedChain) {
+ final List<TrustChainWrapper> result = new ArrayList<>();
+ boolean hints = false;
+ for (final TrustChainWrapper chainWrapper : entities) {
+ if (chainWrapper.isComplete()) {
+ result.add(chainWrapper);
+ continue;
+ }
+ final List<EntityStatement<?>> chain = chainWrapper.getTrustChain();
+ final EntityStatement<?> entityStatement = chain.get(chain.size() - 1);
+ assert entityStatement != null;
+ if (chain.size() > 2 && isLocallyTrusted(entityStatement)
+ && matchesPreSelectedIfSet(chain, preSelectedChain)) {
+ result.add(chainWrapper.complete());
+ }
+ final List<String> authorityHints = entityStatement instanceof EntityConfiguration configuration ?
+ configuration.getParsedPayload().getAuthorityHints() : null;
+ if (authorityHints != null && !authorityHints.isEmpty()) {
+ final List<Pair<EntityConfiguration, SubordinateStatement>> authorities = authorityHints.stream()
+ .filter(id -> verifyNoLoop(chain, id))
+ .filter(id -> verifyPreSelected(chain, id, preSelectedChain))
+ .filter(id -> !EntityStatementHelper.subjectAlreadyExists(id, chain))
+ .map(id -> fetchAuthority(entityStatement, id))
+ .filter(pair -> pair != null && pair.getFirst() != null && pair.getSecond() != null)
+ .toList();
+ hints = !authorities.isEmpty();
+ authorities.forEach(authority -> {
+ final ArrayList<EntityStatement<?>> newChain = new ArrayList<>(chain.size() > 2 ?
+ chain.subList(0, chain.size() - 1) : chain);
+ newChain.add(authority.getSecond());
+ newChain.add(authority.getFirst());
+ result.add(new TrustChainWrapper(newChain));
+ });
+ }
+ }
+ if (hints) {
+ return populateChain(result, preSelectedChain);
+ }
+ return result;
+ }
+
+ /**
+ * Verifies that the given authority is not already included in the given trust chain.
+ *
+ * @param chain trust chain
+ * @param authority entity ID for authority
+ * @return true if not included, false otherwise
+ */
+ protected boolean verifyNoLoop(@Nonnull final List<EntityStatement<?>> chain, @Nullable final String authority) {
+ final List<String> entityIds = EntityStatementHelper.getEntityIds(chain);
+ if (entityIds.contains(authority)) {
+ log.warn("Loop detected: authority hint {} already in the trust chain {} resolved so far", authority,
+ entityIds);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Verifies that the given authority is compliant with the pre-defined trust chain.
+ *
+ * @param chain trust chain
+ * @param authority entity ID for authority
+ * @param preSelectedChain pre-defined trust chain
+ * @return true if authority is compliant, false otherwise
+ */
+ protected boolean verifyPreSelected(@Nonnull final List<EntityStatement<?>> chain, @Nullable final String authority,
+ @Nonnull final List<String> preSelectedChain) {
+ if (preSelectedChain.isEmpty()) {
+ return true;
+ }
+ if (preSelectedChain.size() <= chain.size() || !preSelectedChain.get(chain.size()).equals(authority)) {
+ log.debug("Ignoring authority {} as it doesn't match with the preselected chain {}", authority,
+ preSelectedChain);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the given trust chain matches with the pre-selected trust chain if if it has been set.
+ *
+ * @param chain trust chain
+ * @param preSelectedChain pre-selected trust chain entity IDs
+ * @return true if pre-selected trust chain is empty or is matching the given trust chain
+ */
+ protected boolean matchesPreSelectedIfSet(@Nonnull final List<EntityStatement<?>> chain,
+ @Nonnull final List<String> preSelectedChain) {
+ if (preSelectedChain.isEmpty()) {
+ return true;
+ }
+ return preSelectedChain.equals(EntityStatementHelper.getEntityIds(chain));
+ }
+
+ /**
+ * Fetch authority's entity configuration and subordinate statement for the given entity statement and
+ * authority hint.
+ *
+ * @param entity the entity statement whose authority and subordinate statement are to be fetched
+ * @param authorityHint the authority entity ID
+ * @return pair consisting of authority's entity configuration and subordinate statement, or null if they could not
+ * be fetched.
+ */
+ @Nullable protected Pair<EntityConfiguration, SubordinateStatement> fetchAuthority(
+ @Nonnull final EntityStatement<?> entity, @Nullable final String authorityHint) {
+ final String entityId = entity.getSubject();
+ if (authorityHint == null) {
+ log.error("Entity ID ({}) or authority hint ({}) may not be null", entityId, authorityHint);
+ return null;
+ }
+
+ try {
+ final List<EntityConfigurationContainer> authorityConfigurations =
+ entityConfigurationCache.get(new CriteriaSet(new SubjectEntityIDCriterion(authorityHint)));
+ log.trace("Fetched comfigurations {} for authority {}", authorityConfigurations, authorityHint);
+ final List<SubordinateStatementContainer> subordinateStatements =
+ subordinateStatementCache.get(new CriteriaSet(new SubjectEntityIDCriterion(entityId),
+ new IssuerEntityIDCriterion(authorityHint)));
+ log.trace("Fetched subordinate statements {} for authority {}", subordinateStatements, authorityHint);
+ return new Pair<>(authorityConfigurations.isEmpty() ? null : authorityConfigurations.get(0).getStatement(),
+ subordinateStatements.isEmpty() ? null : subordinateStatements.get(0).getStatement());
+ } catch (final MetadataCacheException e) {
+ log.error("Could not resolve authority hint {} for {}", authorityHint, entityId);
+ return null;
+ }
+ }
+
+ /**
+ * Wrapper class containing trust chain (potentially under construction) and a flag for indicating whether it's
+ * complete.
+ */
+ private class TrustChainWrapper {
+
+ /** Trust chain (potentially incomplete). */
+ private List<EntityStatement<?>> trustChain;
+
+ /** Flag indicating whether the trust chain is complete. */
+ private boolean complete;
+
+ /**
+ * Constructor.
+ *
+ * @param chain trust chain (potentially incomplete) - if the trust anchor (last item at the chain) does not
+ * contain authority hints, the chain is considered as complete
+ * @throws ConstraintViolationException if the last item is not an {@link EntityConfiguration}
+ */
+ protected TrustChainWrapper(@Nonnull final List<EntityStatement<?>> chain)
+ throws ConstraintViolationException {
+ trustChain = chain;
+ if (chain.get(chain.size() - 1) instanceof EntityConfiguration entityConfiguration) {
+ final List<String> authorityHints = entityConfiguration.getParsedPayload().getAuthorityHints();
+ if (authorityHints == null || authorityHints.isEmpty()) {
+ complete = true;
+ } else {
+ complete = false;
+ }
+ } else {
+ throw new ConstraintViolationException("Unexpected entity statement type as last item: "
+ + chain.get(chain.size() - 1));
+ }
+ }
+
+ /**
+ * Get the trust chain.
+ *
+ * @return trust chain
+ */
+ protected List<EntityStatement<?>> getTrustChain() {
+ return trustChain;
+ }
+
+ /**
+ * Set this wrapper as complete.
+ *
+ * @return this wrapper
+ */
+ protected TrustChainWrapper complete() {
+ complete = true;
+ return this;
+ }
+
+ /**
+ * Get whether this wrapper is complete.
+ *
+ * @return true if complete, false otherwise
+ */
+ protected boolean isComplete() {
+ return complete;
+ }
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainSignatureValidationFilterStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainSignatureValidationFilterStrategy.java
new file mode 100644
index 0000000..9f07dc5
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainSignatureValidationFilterStrategy.java
@@ -0,0 +1,176 @@
+/*
+ * 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.oidfed.metadata.cache.trustchain;
+
+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 net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.cache.AbstractTrustEngineSignatureValidationComponent;
+import net.shibboleth.oidfed.metadata.cache.SignatureValidationKeyContainerJwtCriterion;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+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 chains. The signature for each entity statement within trust chain is
+ * verified by using the contents of the jwks-claim of the payload of the next entity statement in the chain until the
+ * final subordinate statement of the chain. The configurable {@link TrustEngine} is used for validating the signature.
+ * The trust engine is fed with the JWT to be verified and the {@link SignatureValidationKeyContainerJwtCriterion}
+ * containing the JWT with the keyset used for validation.
+ *
+ * The trust anchor signature validation is performed by passing the trust anchor entity statement of each trust chain
+ * to the configurable validation filter strategy.
+ *
+ * All the chains that don't pass the full validation are filtered out from the result.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustChainSignatureValidationFilterStrategy
+ extends AbstractTrustEngineSignatureValidationComponent
+ implements BiFunction<TrustChainsContainer, MetadataFilterContext, TrustChainsContainer> {
+
+ /** Class logger. */
+ @Nonnull private Logger log =
+ LoggerFactory.getLogger(DefaultTrustChainSignatureValidationFilterStrategy.class);
+
+ /** Signature validation filter strategy for trust anchor entity statements. */
+ @NonnullAfterInit private BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>>
+ entityStatementSignatureValidationFilterStrategy;
+
+ /** List of custom metadata filter strategies. */
+ @Nonnull private List<BiFunction<TrustChainsContainer,
+ MetadataFilterContext, TrustChainsContainer>> customFilterStrategies;
+
+ /**
+ * Constructor.
+ */
+ public DefaultTrustChainSignatureValidationFilterStrategy() {
+ customFilterStrategies = CollectionSupport.emptyList();
+ }
+
+ /**
+ * Set the signature validation filter strategy for trust anchor entity statements.
+ *
+ * @param strategy signature validation filter strategy
+ */
+ public void setEntityStatementSignatureValidationFilterStrategy(@Nonnull final
+ BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>> strategy) {
+ checkSetterPreconditions();
+ entityStatementSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
+ "EntityStatementSignatureValidationFilterStrategy cannot be null");
+ }
+
+ /**
+ * Set the list of custom metadata filter strategies.
+ *
+ * @param strategies metadata filter strategies
+ */
+ public void setCustomFilterStrategies(@Nullable final List<BiFunction<TrustChainsContainer,
+ MetadataFilterContext, TrustChainsContainer>> strategies) {
+ checkSetterPreconditions();
+ customFilterStrategies = strategies == null ? CollectionSupport.emptyList() : strategies;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (entityStatementSignatureValidationFilterStrategy == null) {
+ throw new ComponentInitializationException(
+ "EntityStatementSignatureValidationFilterStrategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public TrustChainsContainer apply(@Nullable final TrustChainsContainer container,
+ @Nullable final MetadataFilterContext filterContext) {
+ checkComponentActive();
+ if (container == null) {
+ return null;
+ }
+
+ trustChainLoop: for (final List<EntityStatement<?>> trustChain : container.getTrustChains()) {
+ for (int i = 0; i < trustChain.size() - 2; i++) {
+ final EntityStatement<?> trustChainEntry = trustChain.get(i);
+ final SignedJWT keyContainer = trustChain.get(i + 1).getJwt();
+ final CriteriaSet criteria =
+ new CriteriaSet(new SignatureValidationKeyContainerJwtCriterion(keyContainer));
+ if (!validateJwt(trustChainEntry.getJwt(), criteria,
+ trustChain.get(i).getSubject())) {
+ log.warn("The signature check for {} failed, ignoring trust chain anchored by {}",
+ i == 0 ? "leaf entity configuration" : "subordinate statement",
+ trustChain.get(trustChain.size() - 1).getSubject());
+ log.trace("Key container {} could not validate the trust chain entry {}", keyContainer.serialize(),
+ trustChainEntry.getJwt().serialize());
+ container.removeTrustChain(trustChain);
+ continue trustChainLoop;
+ }
+ }
+
+ final EntityStatement<?> trustAnchorSubordinateStatement = trustChain.get(trustChain.size() - 2);
+ final String subjectId = trustAnchorSubordinateStatement.getSubject();
+ final String trustAnchorId = trustAnchorSubordinateStatement.getIssuer();
+ final EntityStatement<?> filteredSubordinateStatement =
+ entityStatementSignatureValidationFilterStrategy.apply(trustAnchorSubordinateStatement,
+ filterContext);
+ if (filteredSubordinateStatement != null) {
+ log.debug("Successfully validated subordinate statement for {}, issued by trust anchor {}",
+ subjectId, trustAnchorId);
+ } else {
+ log.warn("Signature validation failed for subordinate statement for {}, issued by trust anchor {}",
+ subjectId, trustAnchorId);
+ container.removeTrustChain(trustChain);
+ continue trustChainLoop;
+ }
+
+ final EntityStatement<?> trustAnchorStatement = trustChain.get(trustChain.size() - 1);
+ final EntityStatement<?> filteredStatement =
+ entityStatementSignatureValidationFilterStrategy.apply(trustAnchorStatement, filterContext);
+ if (filteredStatement != null) {
+ log.debug("Successfully validated entity statement for trust anchor {}", trustAnchorId);
+ } else {
+ log.warn("Signature validation failed for trust anchor {}", trustAnchorId);
+ container.removeTrustChain(trustChain);
+ }
+ }
+
+ TrustChainsContainer result = container;
+ for (final BiFunction<TrustChainsContainer, MetadataFilterContext,
+ TrustChainsContainer> customFilterStrategy : customFilterStrategies) {
+ log.trace("Applying custom filter strategy {}", customFilterStrategy);
+ result = customFilterStrategy.apply(result, filterContext);
+ if (result == null) {
+ log.debug("Custom filter strategy {} returned null", customFilterStrategy);
+ return null;
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainSubjectIdentifierExtractionStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainSubjectIdentifierExtractionStrategy.java
new file mode 100644
index 0000000..57b44c8
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainSubjectIdentifierExtractionStrategy.java
@@ -0,0 +1,45 @@
+/*
+ * 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.oidfed.metadata.cache.trustchain;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+
+/**
+ * Default identifier strategy for the list of trust chains: the entity ID of the leaf entity is always used.
+ */
+ at ThreadSafe
+public class DefaultTrustChainSubjectIdentifierExtractionStrategy
+ implements Function<List<List<EntityStatement<?>>>, String> {
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public String apply(@Nullable final List<List<EntityStatement<?>>> trustChains) {
+ if (trustChains == null || trustChains.isEmpty()) {
+ return null;
+ }
+ final List<EntityStatement<?>> firstChain = trustChains.get(0);
+ if (firstChain == null || firstChain.isEmpty() || firstChain.get(0) == null) {
+ return null;
+ }
+ return firstChain.get(0).getSubject();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/ProvidedTrustChainContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/ProvidedTrustChainContainer.java
new file mode 100644
index 0000000..69e697d
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/ProvidedTrustChainContainer.java
@@ -0,0 +1,49 @@
+/*
+ * 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.oidfed.metadata.cache.trustchain;
+
+import java.time.Instant;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Metadata cache container for provided trust chain. The expiration is hardcoded to five minutes as it's assumed
+ * that the container is solely used with fetch-through caches and the expiration validation is done during the
+ * filtering phase.
+ */
+public class ProvidedTrustChainContainer extends TrustChainsContainer {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -6322735111721678470L;
+
+ /**
+ * Constructor.
+ *
+ * @param trustChain trust chain
+ * @param entity entity ID for the trust chain subject
+ */
+ public ProvidedTrustChainContainer(@Nonnull final List<EntityStatement<?>> trustChain,
+ @Nonnull @NotEmpty final String entity) {
+ super(Constraint.isNotNull(Instant.now().plusSeconds(300), "Expiration instant cannot be null"),
+ entity, CollectionSupport.listOf(trustChain));
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/ProvidedTrustChainContainerCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/ProvidedTrustChainContainerCriterion.java
new file mode 100644
index 0000000..ca6c863
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/ProvidedTrustChainContainerCriterion.java
@@ -0,0 +1,81 @@
+/*
+ * 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.oidfed.metadata.cache.trustchain;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a provided trust chain container.
+ */
+public class ProvidedTrustChainContainerCriterion implements Criterion {
+
+ /** The trust chain container value. */
+ @Nonnull final ProvidedTrustChainContainer value;
+
+ /**
+ * Constructor.
+ *
+ * @param container the trust chain container value, must not be null
+ */
+ public ProvidedTrustChainContainerCriterion(
+ @Nonnull final ProvidedTrustChainContainer container) {
+ value = Constraint.isNotNull(container, "Trust chain container cannot be null");
+ }
+
+ /**
+ * Get the trust chain container value.
+ *
+ * @return the container value
+ */
+ @Nonnull public ProvidedTrustChainContainer getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "ProvidedTrustChainContainerCriterion [value=" + value + "]";
+ }
+
+ /** {@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 ProvidedTrustChainContainerCriterion other = (ProvidedTrustChainContainerCriterion) obj;
+ final var chains = value.getTrustChains();
+ final var otherChains = other.value.getTrustChains();
+ return value.getEntityId().equals(other.value.getEntityId()) && chains.containsAll(otherChains)
+ && otherChains.containsAll(chains);
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/TrustChainsContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/TrustChainsContainer.java
new file mode 100644
index 0000000..b510fb0
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/TrustChainsContainer.java
@@ -0,0 +1,98 @@
+/*
+ * 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.oidfed.metadata.cache.trustchain;
+
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableMetadataContainer;
+import net.shibboleth.shared.annotation.constraint.Live;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A container class for metadata caches carrying ID and trust chains details related to Trust Chains of the entity.
+ */
+public class TrustChainsContainer extends BaseExpirableMetadataContainer implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 656434369356432370L;
+
+ /** Requested entity ID. */
+ @Nonnull @NotEmpty private final String entityId;
+
+ /** Trust chains for this container. */
+ @Nonnull @NonnullElements @Live private final List<List<EntityStatement<?>>> trustChains;
+
+ /**
+ * Constructor.
+ *
+ * @param expirationInstant expiration instant, must not be null
+ * @param entity entity ID, must not be null
+ */
+ public TrustChainsContainer(@Nonnull final Instant expirationInstant,
+ @Nonnull @NotEmpty final String entity) {
+ this(expirationInstant, entity, CollectionSupport.emptyList());
+ }
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param expirationInstant expiration instant, must not be null
+ * @param entity entity ID, must not be null
+ * @param chains initial trust chains value
+ */
+ public TrustChainsContainer(@Nonnull final Instant expirationInstant, @Nonnull @NotEmpty final String entity,
+ @Nonnull @NonnullElements final List<List<EntityStatement<?>>> chains) {
+ super(expirationInstant);
+ entityId = Constraint.isNotNull(entity, "Entity ID cannot be empty");
+ trustChains = new ArrayList<>(Constraint.isNotNull(chains, "Trust chains cannot be null"));
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull @NotEmpty
+ public String getEntityId() {
+ return entityId;
+ }
+
+ /**
+ * Get the list of trust chains.
+ *
+ * @return list of trust chains
+ */
+ @Nonnull @NonnullElements public List<List<EntityStatement<?>>> getTrustChains() {
+ return CollectionSupport.copyToList(trustChains);
+ }
+
+ /**
+ * Remove the given trust chain from the list of trust chains.
+ *
+ * @param trustChain trust chain to be removed
+ * @return this object
+ */
+ @Nonnull public TrustChainsContainer removeTrustChain(
+ @Nonnull @NonnullElements final List<EntityStatement<?>> trustChain) {
+ trustChains.remove(trustChain);
+ return this;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkFetchingStrategy.java
new file mode 100644
index 0000000..1b6a789
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkFetchingStrategy.java
@@ -0,0 +1,158 @@
+/*
+ * 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.oidfed.metadata.cache.trustmark;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+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.HttpRequest;
+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.apache.hc.core5.net.URIBuilder;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.TrustMark;
+import net.shibboleth.oidfed.metadata.cache.AbstractFederationEndpointResponseFetchingStrategy;
+import net.shibboleth.oidfed.metadata.impl.TrustMarkImpl;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for fetching trust mark via Trust Mark API for the request specified in the criteria set. The
+ * response is stored inside a {@link TrustMarkContainer}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustMarkFetchingStrategy
+ extends AbstractFederationEndpointResponseFetchingStrategy<TrustMarkCacheIdentifier, TrustMarkContainer> {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("trust-mark+jwt");
+
+ /** The HTTP response content type. */
+ @Nonnull public static final String HTTP_RESPONSE_CONTENT_TYPE = "application/" + JWT_TYPE_HEADER.toString();
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustMarkFetchingStrategy.class);
+
+ /**
+ * Constructor.
+ */
+ public DefaultTrustMarkFetchingStrategy() {
+ setCriteriaToRequestDataStrategy(criteria -> {
+ final TrustMarkIdentifierCriterion identifierCriterion = criteria.get(TrustMarkIdentifierCriterion.class);
+ if (identifierCriterion == null) {
+ log.debug("No identifier criterion given, returning null");
+ return null;
+ }
+ return identifierCriterion.getIdentifier();
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+ @Nonnull TrustMarkCacheIdentifier request) {
+ final List<NameValuePair> nvps = new ArrayList<>();
+ nvps.add(new BasicNameValuePair("trust_mark_type", request.getTrustMarkType()));
+ nvps.add(new BasicNameValuePair("sub", request.getSubject()));
+ final BiConsumer<HttpRequest,List<NameValuePair>> clientAuthentication = getClientAuthentication(criteria);
+ if (clientAuthentication == null) {
+ final HttpGet httpRequest = new HttpGet(request.getEndpoint());
+ try {
+ final URI uri = new URIBuilder(httpRequest.getUri()).addParameters(nvps).build();
+ httpRequest.setUri(uri);
+ } catch (final URISyntaxException e) {
+ log.error("Could not create URI with the given parameters {}", request, e);
+ }
+ return httpRequest;
+ } else {
+ final HttpPost httpRequest = new HttpPost(request.getEndpoint());
+ httpRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED);
+ clientAuthentication.accept(httpRequest, nvps);
+ final UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(
+ nvps, StandardCharsets.UTF_8);
+ httpRequest.setEntity(urlEncodedFormEntity);
+ return httpRequest;
+ }
+
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected TrustMarkContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
+ @Nonnull final TrustMarkCacheIdentifier request, @Nullable final ClassicHttpResponse response,
+ @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+ @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+ if (response != null) {
+ if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
+ log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+ return new TrustMarkContainer(request, null, validExpiration, invalidExpiration);
+ }
+
+ try {
+ final SignedJWT jwt = SignedJWT.parse(EntityUtils.toString(response.getEntity()));
+ if (!JWT_TYPE_HEADER.equals(jwt.getHeader().getType())) {
+ log.warn("Unexpected JWT type header {}", jwt.getHeader().getType());
+ return new TrustMarkContainer(request, null, validExpiration, invalidExpiration);
+ }
+ assert objectMapper != null;
+ final TrustMark trustMark = TrustMarkImpl.parse(jwt, objectMapper);
+ return new TrustMarkContainer(
+ request, trustMark, validExpiration, invalidExpiration);
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not deserialize entity statement payload from the response", e);
+ } catch (final ConstraintViolationException e) {
+ log.warn("Parsed entity configuration claims did not pass syntax validation", e);
+ } catch (java.text.ParseException e) {
+ log.warn("Could not parse JWT from the response", e);
+ }
+ return new TrustMarkContainer(request, null, validExpiration, invalidExpiration);
+ } else {
+ log.debug("Unable to fetch trust mark: {} (null response)", request.getTrustMarkType());
+ return new TrustMarkContainer(request, null, validExpiration, nullExpiration);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected TrustMarkContainer handleException(@Nonnull final CriteriaSet criteria,
+ @Nonnull final TrustMarkCacheIdentifier request, @Nonnull final Throwable exception,
+ @Nonnull final Instant expiration) {
+ return new TrustMarkContainer(request, null, expiration, expiration);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkStatusFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkStatusFetchingStrategy.java
new file mode 100644
index 0000000..c107349
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkStatusFetchingStrategy.java
@@ -0,0 +1,144 @@
+/*
+ * 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.oidfed.metadata.cache.trustmark;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+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.HttpRequest;
+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.fasterxml.jackson.core.JsonProcessingException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.TrustMarkStatus;
+import net.shibboleth.oidfed.metadata.cache.AbstractFederationEndpointResponseFetchingStrategy;
+import net.shibboleth.oidfed.metadata.impl.TrustMarkStatusImpl;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+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 TrustMarkStatusContainer}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustMarkStatusFetchingStrategy
+ extends AbstractFederationEndpointResponseFetchingStrategy
+ <TrustMarkStatusCacheIdentifier, TrustMarkStatusContainer> {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("trust-mark-status-response+jwt");
+
+ /** The HTTP response content type. */
+ @Nonnull public static final String HTTP_RESPONSE_CONTENT_TYPE = "application/" + JWT_TYPE_HEADER.toString();
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustMarkStatusFetchingStrategy.class);
+
+ /**
+ * Constructor.
+ */
+ public DefaultTrustMarkStatusFetchingStrategy() {
+ setCriteriaToRequestDataStrategy(criteria -> {
+ final TrustMarkStatusIdentifierCriterion identifierCriterion =
+ criteria.get(TrustMarkStatusIdentifierCriterion.class);
+ if (identifierCriterion == null) {
+ log.debug("No identifier criterion given, returning null");
+ return null;
+ }
+ return identifierCriterion.getIdentifier();
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+ @Nonnull TrustMarkStatusCacheIdentifier identifier) {
+ final HttpPost httpRequest = new HttpPost(identifier.getEndpoint());
+ final List<NameValuePair> nvps = new ArrayList<>();
+ nvps.add(new BasicNameValuePair("trust_mark", identifier.getTrustMark().serialize()));
+ final BiConsumer<HttpRequest,List<NameValuePair>> clientAuthentication = getClientAuthentication(criteria);
+ if (clientAuthentication != null) {
+ clientAuthentication.accept(httpRequest, nvps);
+ }
+ 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 TrustMarkStatusContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
+ @Nonnull final TrustMarkStatusCacheIdentifier identifier, @Nullable final ClassicHttpResponse response,
+ @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+ @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+ if (response != null) {
+ if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
+ log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+ return new TrustMarkStatusContainer(identifier, null, validExpiration, invalidExpiration);
+ }
+
+ try {
+ final SignedJWT jwt = SignedJWT.parse(EntityUtils.toString(response.getEntity()));
+ if (!JWT_TYPE_HEADER.equals(jwt.getHeader().getType())) {
+ log.warn("Unexpected JWT type header {}", jwt.getHeader().getType());
+ return new TrustMarkStatusContainer(identifier, null, validExpiration, invalidExpiration);
+ }
+ assert objectMapper != null;
+ final TrustMarkStatus trustMarkStatus = TrustMarkStatusImpl.parse(jwt, objectMapper);
+ return new TrustMarkStatusContainer(
+ identifier, trustMarkStatus, validExpiration, invalidExpiration);
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not deserialize trust mark status payload from the response", e);
+ } catch (final ConstraintViolationException e) {
+ log.warn("Parsed trust mark status claims did not pass syntax validation", e);
+ } catch (java.text.ParseException e) {
+ log.warn("Could not parse JWT from the response", e);
+ }
+ return new TrustMarkStatusContainer(identifier, null, validExpiration, invalidExpiration);
+ } else {
+ log.debug("Unable to fetch trust mark status: {} (null response)", identifier.getEndpoint());
+ return new TrustMarkStatusContainer(identifier, null, validExpiration, nullExpiration);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable protected TrustMarkStatusContainer handleException(@Nonnull final CriteriaSet criteria,
+ @Nonnull final TrustMarkStatusCacheIdentifier identifier, @Nonnull final Throwable exception,
+ @Nonnull final Instant expiration) {
+ return new TrustMarkStatusContainer(identifier, null, expiration, expiration);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkValidationCriteriaSetLookupFunction.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkValidationCriteriaSetLookupFunction.java
new file mode 100644
index 0000000..5d2af21
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkValidationCriteriaSetLookupFunction.java
@@ -0,0 +1,124 @@
+/*
+ * 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.oidfed.metadata.cache.trustmark;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidfed.metadata.BaseJWTWrapper;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.oidfed.metadata.cache.IssuerEntityStatementCriterion;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityIDCriterion;
+import net.shibboleth.oidfed.metadata.cache.SubjectStatementCriterion;
+import net.shibboleth.oidfed.metadata.cache.trustchain.TrustChainsContainer;
+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 criteria set lookup strategy for {@link BaseExpirableStatementContainer} that fetches the issuer
+ * entity statement via trust chain cache.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustMarkValidationCriteriaSetLookupFunction
+ extends AbstractIdentifiableInitializableComponent
+ implements Function<BaseExpirableStatementContainer<?>, CriteriaSet> {
+
+ /** Class logger. */
+ @Nonnull
+ private Logger log = LoggerFactory.getLogger(DefaultTrustMarkValidationCriteriaSetLookupFunction.class);
+
+ /** Cache used to fetch the issuer entity configuration from. */
+ @NonnullAfterInit private MetadataCache<TrustChainsContainer> 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<TrustChainsContainer> 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 CriteriaSet apply(@Nullable final BaseExpirableStatementContainer<?> metadataContainer) {
+ if (metadataContainer != null && metadataContainer.getStatement() != null) {
+ final BaseJWTWrapper<?> statement = metadataContainer.getStatement();
+ assert statement != null;
+ final SubjectStatementCriterion subjectCriterion = new SubjectStatementCriterion(statement);
+ final IssuerEntityStatementCriterion issuerCriterion = getIssuerCriterion(statement);
+ if (issuerCriterion == null) {
+ log.debug("Issuer statement could not be fetched, returning null");
+ return null;
+ }
+ return new CriteriaSet(subjectCriterion, issuerCriterion);
+ }
+ log.debug("Entity statement is not found from the container, returning null");
+ return null;
+ }
+
+ /**
+ * Fetch the issuer entity configuration from the metadata cache and initialize the criterion.
+ *
+ * @param statement the entity statement whose issuer is fetched
+ * @return the issuer entity configuration, or null if could not be fetched
+ */
+ @Nullable protected IssuerEntityStatementCriterion getIssuerCriterion(
+ @Nonnull final BaseJWTWrapper<?> statement) {
+ final String issuer = statement.getIssuer();
+ final CriteriaSet criteria = new CriteriaSet(new SubjectEntityIDCriterion(issuer));
+ try {
+ final List<TrustChainsContainer> result = trustChainCache.get(criteria);
+ if (!result.isEmpty()) {
+ final List<List<EntityStatement<?>>> chains = result.get(0).getTrustChains();
+ if (!chains.isEmpty()) {
+ final EntityStatement<?> issuerStatement = chains.get(0).get(0);
+ assert issuerStatement != null;
+ return new IssuerEntityStatementCriterion(issuerStatement);
+ }
+ }
+ } 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/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkCacheIdentifier.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkCacheIdentifier.java
new file mode 100644
index 0000000..7d82c7e
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkCacheIdentifier.java
@@ -0,0 +1,120 @@
+/*
+ * 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.oidfed.metadata.cache.trustmark;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Default identifier for trust mark within {@link TrustMarkContainer}.
+ */
+public class TrustMarkCacheIdentifier implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -4143516153785776493L;
+
+ /** The endpoint URI of the request. */
+ @Nonnull @NotEmpty private final String endpoint;
+
+ /** The identifier for the type of the Trust Mark. */
+ @Nonnull @NotEmpty private final String trustMarkType;
+
+ /** The entity ID of the Entity to which the Trust Mark is issued. */
+ @Nonnull @NotEmpty private final String subject;
+
+ /**
+ * Constructor.
+ *
+ * @param uri endpoint URI
+ * @param type trust _mark type
+ * @param sub subject entity ID
+ */
+ public TrustMarkCacheIdentifier(@Nonnull @NotEmpty final String uri, @Nonnull @NotEmpty final String type,
+ @Nonnull @NotEmpty final String sub) {
+ endpoint = Constraint.isNotEmpty(uri, "Endpoint URI cannot be null");
+ trustMarkType = Constraint.isNotEmpty(type, "Trust Mark type cannot be null or empty");
+ subject = Constraint.isNotEmpty(sub, "Subject cannot be null or empty");
+ }
+
+ /**
+ * Get the endpoint for the request.
+ *
+ * @return endpoint
+ */
+ @Nonnull @NotEmpty public String getEndpoint() {
+ return endpoint;
+ }
+
+ /**
+ * Get the trust mark type for the request.
+ *
+ * @return trust mark type
+ */
+ @Nonnull @NotEmpty public String getTrustMarkType() {
+ return trustMarkType;
+ }
+
+ /**
+ * Get the subject for the request.
+ *
+ * @return subject
+ */
+ @Nonnull @NotEmpty public String getSubject() {
+ return subject;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("subject", getSubject())
+ .add("trustMarkType", getTrustMarkType())
+ .add("endpoint", getEndpoint())
+ .toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(endpoint, subject, trustMarkType);
+ }
+
+ /** {@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 TrustMarkCacheIdentifier other = (TrustMarkCacheIdentifier) obj;
+ return endpoint.equals(other.endpoint) && subject.equals(other.subject)
+ && trustMarkType.equals(other.trustMarkType);
+
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkContainer.java
new file mode 100644
index 0000000..50075d9
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkContainer.java
@@ -0,0 +1,68 @@
+/*
+ * 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.oidfed.metadata.cache.trustmark;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.TrustMark;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A container class for metadata caches carrying ID and statement details related to Trust Mark.
+ */
+public class TrustMarkContainer extends BaseExpirableStatementContainer<TrustMark> implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 756269369356865370L;
+
+ /** Requested entity ID. */
+ @Nonnull private final TrustMarkCacheIdentifier identifier;
+
+ /**
+ * Constructor.
+ *
+ * @param id identifier, must not be null
+ * @param trustMark trust mark parsed from the response
+ * @param validExpirationInstant expiration instant for valid statement, must not be null
+ * @param invalidExpirationInstant expiration instant for invalid statement, must not be null
+ */
+ public TrustMarkContainer(@Nonnull final TrustMarkCacheIdentifier id,
+ @Nullable final TrustMark trustMark, @Nonnull final Instant validExpirationInstant,
+ @Nonnull final Instant invalidExpirationInstant) {
+ super(validExpirationInstant, trustMark, invalidExpirationInstant);
+ identifier = Constraint.isNotNull(id, "Identifier cannot be null");
+ }
+
+ /**
+ * Get entity ID.
+ *
+ * @return entity ID
+ */
+ @Nonnull @NotEmpty public TrustMarkCacheIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getEntityId() {
+ return identifier.getSubject();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkIdentifierCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkIdentifierCriterion.java
new file mode 100644
index 0000000..960874a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkIdentifierCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.cache.trustmark;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing request message details to a Trust Mark API.
+ */
+public class TrustMarkIdentifierCriterion implements Criterion {
+
+ /** The trust mark identifier. */
+ @Nonnull private final TrustMarkCacheIdentifier identifier;
+
+ /**
+ * Constructor.
+ *
+ * @param id identifier, must not be null
+ */
+ public TrustMarkIdentifierCriterion(@Nonnull final TrustMarkCacheIdentifier id) {
+ identifier = Constraint.isNotNull(id, "Identifier cannot be null");
+ }
+
+ /**
+ * Get the trust mark identifier.
+ *
+ * @return the identifier
+ */
+ @Nonnull public TrustMarkCacheIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "TrustMarkIdentifierCriterion [identifier=" + identifier + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(identifier);
+ }
+
+ /** {@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 TrustMarkIdentifierCriterion other = (TrustMarkIdentifierCriterion) obj;
+ return identifier.equals(other.identifier);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusCacheIdentifier.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusCacheIdentifier.java
new file mode 100644
index 0000000..f074027
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusCacheIdentifier.java
@@ -0,0 +1,103 @@
+/*
+ * 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.oidfed.metadata.cache.trustmark;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.MoreObjects;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Request message to the OpenID federation Trust Mark Status endpoint.
+ */
+public class TrustMarkStatusCacheIdentifier implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -4143643153785776493L;
+
+ /** The endpoint URI of the request. */
+ @Nonnull private final String endpoint;
+
+ /** The trust mark for the request. */
+ @Nonnull private final SignedJWT trustMark;
+
+ /**
+ * Constructor.
+ *
+ * @param uri endpoint URI
+ * @param jwt trust mark
+ */
+ public TrustMarkStatusCacheIdentifier(@Nonnull @NotEmpty final String uri, @Nonnull final SignedJWT jwt) {
+ endpoint = Constraint.isNotEmpty(uri, "Endpoint URI cannot be null");
+ trustMark = Constraint.isNotNull(jwt, "Trust Mark JWT cannot be null");
+ }
+
+ /**
+ * Get the endpoint URI of the request.
+ *
+ * @return endpoint
+ */
+ @Nonnull @NotEmpty public String getEndpoint() {
+ return endpoint;
+ }
+
+ /**
+ * Get the trust mark (serialized JWT) for the request.
+ *
+ * @return trust mark
+ */
+ @Nonnull @NotEmpty public SignedJWT getTrustMark() {
+ return trustMark;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("trustMark", getTrustMark().serialize())
+ .add("endpoint", getEndpoint())
+ .toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(endpoint, trustMark.serialize());
+ }
+
+ /** {@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 TrustMarkStatusCacheIdentifier other = (TrustMarkStatusCacheIdentifier) obj;
+ return endpoint.equals(other.endpoint) && trustMark.serialize().equals(other.trustMark.serialize());
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusContainer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusContainer.java
new file mode 100644
index 0000000..5203876
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusContainer.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.oidfed.metadata.cache.trustmark;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidfed.metadata.TrustMarkStatus;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A container class for metadata caches carrying ID and statement details related to Trust Mark status.
+ */
+public class TrustMarkStatusContainer extends BaseExpirableStatementContainer<TrustMarkStatus> implements Serializable {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 756269369356884270L;
+
+ /** Requested entity ID. */
+ @Nonnull private final TrustMarkStatusCacheIdentifier identifier;
+
+ /**
+ * Constructor.
+ *
+ * @param id identifier, must not be null
+ * @param trustMarkStatus trust mark status parsed from the response
+ * @param validExpirationInstant expiration instant for valid statement, must not be null
+ * @param invalidExpirationInstant expiration instant for invalid statement, must not be null
+ */
+ public TrustMarkStatusContainer(@Nonnull final TrustMarkStatusCacheIdentifier id,
+ @Nullable final TrustMarkStatus trustMarkStatus, @Nonnull final Instant validExpirationInstant,
+ @Nonnull final Instant invalidExpirationInstant) {
+ super(validExpirationInstant, trustMarkStatus, invalidExpirationInstant);
+ identifier = Constraint.isNotNull(id, "Identifier cannot be null");
+ }
+
+ /**
+ * Get entity ID.
+ *
+ * @return entity ID
+ */
+ @Nonnull @NotEmpty public TrustMarkStatusCacheIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getEntityId() {
+ try {
+ final JWTClaimsSet claimsSet = identifier.getTrustMark().getJWTClaimsSet();
+ if (claimsSet != null) {
+ return Constraint.isNotEmpty(claimsSet.getIssuer(), "Issuer of the trust mark cannot be empty");
+ }
+ } catch (final ParseException e) {
+ // no op
+ }
+ throw new ConstraintViolationException("Could not resolve issuer of the trust mark");
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusIdentifierCriterion.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusIdentifierCriterion.java
new file mode 100644
index 0000000..bd55089
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/TrustMarkStatusIdentifierCriterion.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.cache.trustmark;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing request message details to a Trust Mark Status API.
+ */
+public class TrustMarkStatusIdentifierCriterion implements Criterion {
+
+ /** The trust mark status identifier. */
+ @Nonnull private final TrustMarkStatusCacheIdentifier identifier;
+
+ /**
+ * Constructor.
+ *
+ * @param id identifier, must not be null
+ */
+ public TrustMarkStatusIdentifierCriterion(@Nonnull final TrustMarkStatusCacheIdentifier id) {
+ identifier = Constraint.isNotNull(id, "Identifier cannot be null");
+ }
+
+ /**
+ * Get the trust mark status identifier.
+ *
+ * @return the identifier
+ */
+ @Nonnull public TrustMarkStatusCacheIdentifier getIdentifier() {
+ return identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "TrustMarkStatusIdentifierCriterion [identifier=" + identifier + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(identifier);
+ }
+
+ /** {@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 TrustMarkStatusIdentifierCriterion other = (TrustMarkStatusIdentifierCriterion) obj;
+ return identifier.equals(other.identifier);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/FederationPolicyConstraint.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/FederationPolicyConstraint.java
new file mode 100644
index 0000000..9ff6e7a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/FederationPolicyConstraint.java
@@ -0,0 +1,42 @@
+/*
+ * 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.oidfed.metadata.constraints;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.shared.annotation.constraint.Live;
+
+/**
+ * Interface to be implemented by the federation policy constraints.
+ */
+public interface FederationPolicyConstraint {
+
+ /**
+ * Validate whether the given trust chain meets given federation policy constraint value. The trust chain may be
+ * modified by the constraint validators, thus it is required to be modifiable.
+ *
+ * @param constraint the constraint value
+ * @param subordinateStatement subordinate statement that defined the constraint
+ * @param trustChain trust chain to be evaluated and optionally updated: it is required to be modifiable
+ * @return true if the trust chain is valid for this constraint, false otherwise
+ */
+ public boolean validate(@Nullable final Object constraint, @Nonnull final SubordinateStatement subordinateStatement,
+ @Nonnull @Live final List<EntityStatement<?>> trustChain);
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/FederationPolicyConstraintHelper.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/FederationPolicyConstraintHelper.java
new file mode 100644
index 0000000..2b4c7b5
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/FederationPolicyConstraintHelper.java
@@ -0,0 +1,78 @@
+/*
+ * 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.oidfed.metadata.constraints;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.shared.annotation.constraint.Live;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Static utility method related to federation policy constraints.
+ */
+public class FederationPolicyConstraintHelper {
+
+ /** Class logger. */
+ @Nonnull private final static Logger log = LoggerFactory.getLogger(FederationPolicyConstraintHelper.class);
+
+ /**
+ * Parses and verifies federation policy constraints in the given trust chain. Optionally the changed values
+ * (for instance with the 'allowed_entity_types' constraint) are enforced to the trust chain.
+ *
+ * @param trustChain trust chain to be verified and optionally modified: it is required to be modifiable
+ * @param federationPolicyConstraints map of the federation policy constraint implementations
+ * @return true if trust chain meets the constraints. false otherwise
+ */
+ public static boolean verifyPolicyConstraints(@Nonnull @Live final List<EntityStatement<?>> trustChain,
+ @Nonnull Map<String, FederationPolicyConstraint> federationPolicyConstraints) {
+ if (trustChain.size() < 3) {
+ return true;
+ }
+ for (int i = trustChain.size() - 2; i > 0; i--) {
+ if (trustChain.get(i) instanceof SubordinateStatement subordinateStatement) {
+ final Map<String, Object> constraints = subordinateStatement.getParsedPayload().getConstraints();
+ if (constraints == null || constraints.isEmpty()) {
+ log.trace("No constraints found for {}: {}", subordinateStatement.getSubject(), constraints);
+ continue;
+ }
+ for (final String constraint : constraints.keySet()) {
+ final FederationPolicyConstraint validator = federationPolicyConstraints.get(constraint);
+ if (validator != null) {
+ log.trace("Validating federation policy constraint {} with {}", constraint, validator);
+ if (!validator.validate(constraints.get(constraint), subordinateStatement, trustChain)) {
+ log.warn("Subordinate statement issued by {} contained constraint {} that failed",
+ subordinateStatement.getIssuer(), constraint);
+ return false;
+ }
+ } else {
+ log.debug("Ignoring non-recognized federation policy constraint {}", constraint);
+ }
+ }
+ } else {
+ log.error("Unexpected trust chain contents: {} is not a subordinate statement", trustChain.get(i));
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java
new file mode 100644
index 0000000..20c26da
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java
@@ -0,0 +1,82 @@
+/*
+ * 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.oidfed.metadata.constraints.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.constraints.FederationPolicyConstraint;
+import net.shibboleth.shared.annotation.constraint.Live;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Base implementation for the classes implementing {@link FederationPolicyConstraint}.
+ *
+ * @param <T> the data type for the constraint
+ */
+public abstract class AbstractFederationPolicyConstraint<T extends Object>
+ extends AbstractIdentifiableInitializableComponent implements FederationPolicyConstraint {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractFederationPolicyConstraint.class);
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean validate(@Nullable final Object constraint, @Nonnull final SubordinateStatement subordinateStatement,
+ @Nonnull @Live final List<EntityStatement<?>> trustChain) {
+ checkComponentActive();
+ try {
+ log.trace("Attempting to parse raw constraint value: {}", constraint);
+ final T constraintData = parseConstraint(constraint);
+ if (constraintData != null) {
+ return doValidate(constraintData, subordinateStatement, trustChain);
+ } else {
+ return true;
+ }
+ } catch (final ConstraintViolationException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Parses the constraint data from the raw claim object value.
+ *
+ * @param constraint raw object value
+ * @return parsed constraint data
+ * @throws ConstraintViolationException if the parsing was not successful
+ */
+ @Nullable protected abstract T parseConstraint(@Nullable final Object constraint)
+ throws ConstraintViolationException;
+
+ /**
+ * Validate whether the given trust chain meets given federation policy constraint value,
+ *
+ * @param constraintData the non-null constraint value
+ * @param subordinateStatement subordinate statement that contains the constraint
+ * @param trustChain trust chain to be evaluated and optionally updated: it is required to be modifiable
+ * @return true if the trust chain is valid for this constraint, false otherwise.
+ */
+ protected abstract boolean doValidate(@Nonnull final T constraintData,
+ @Nonnull final SubordinateStatement subordinateStatement,
+ @Nonnull @Live final List<EntityStatement<?>> trustChain);
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
new file mode 100644
index 0000000..b1ddfb3
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
@@ -0,0 +1,160 @@
+/*
+ * 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.oidfed.metadata.constraints.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.impl.EntityConfigurationImpl;
+import net.shibboleth.oidfed.metadata.impl.SubordinateStatementImpl;
+import net.shibboleth.oidfed.metadata.payload.claim.Metadata;
+import net.shibboleth.oidfed.metadata.payload.claim.impl.MetadataImpl;
+import net.shibboleth.oidfed.metadata.payload.impl.EntityConfigurationPayloadImpl;
+import net.shibboleth.oidfed.metadata.payload.impl.SubordinateStatementPayloadImpl;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default implementation for the 'allowed_entity_types' -constraint.
+ */
+public class DefaultAllowedEntityTypesConstraint extends AbstractFederationPolicyConstraint<List<String>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultAllowedEntityTypesConstraint.class);
+
+ /** JSON object mapper used for decoding JSON into Map. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /**
+ * 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 (objectMapper == null) {
+ throw new ComponentInitializationException("Object mapper cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ protected List<String> parseConstraint(@Nullable final Object constraint)
+ throws ConstraintViolationException {
+ if (constraint instanceof List<?> list) {
+ return list.stream().filter(String.class::isInstance).map(String::valueOf).toList();
+ } else if (constraint != null) {
+ throw new ConstraintViolationException("Unexpected value type for allowed_entity_types: " + constraint);
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doValidate(@Nonnull final List<String> constraintData,
+ @Nonnull final SubordinateStatement subordinateStatement,
+ @Nonnull final List<EntityStatement<?>> trustChain) {
+ if (constraintData.contains("federation_entity")) {
+ log.warn("The value 'federation_entity' is not allowed for allowed_entity_types");
+ return false;
+ }
+ final List<String> allowedTypes = new ArrayList<>(constraintData);
+ allowedTypes.add("federation_entity");
+ log.trace("Allowed entity types: {}", allowedTypes);
+ for (int i = 0; i < trustChain.size() - 1; i++) {
+ final EntityStatement<?> entityStatement = trustChain.get(i);
+ assert entityStatement != null;
+ if (subordinateStatement.equals(entityStatement)) {
+ break;
+ }
+ final Metadata metadata =
+ Optional.ofNullable(entityStatement.getParsedPayload().getMetadata())
+ .orElse(null);
+ if (metadata != null) {
+ for (final String entityType : metadata.getAllClaims().keySet()) {
+ if (!allowedTypes.contains(entityType)) {
+ log.warn("The entity type {} is not allowed in entity statement {}", entityType,
+ entityStatement.getSubject());
+ for (int j = 0; j <= i; j++) {
+ final EntityStatement<?> replacement = removeMetadata(entityType, trustChain.get(j));
+ if (replacement == null) {
+ log.error("Could not remove the entity type {} from metadata for {}", entityType,
+ entityStatement.getSubject());
+ return false;
+ }
+ trustChain.set(j, replacement);
+ }
+ } else {
+ log.trace("The entity type {} is allowed for {}", entityType, entityStatement.getSubject());
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Removes the metadata of given entity type from the given entity statement.
+ *
+ * @param entityType entity type for metadata to be removed
+ * @param entityStatement entity statement holding the metadata
+ * @return updated entity statement, or null if the operation failed
+ */
+ @Nullable private EntityStatement<?> removeMetadata(final String entityType,
+ final EntityStatement<?> entityStatement) {
+ final Map<String, Map<String, Object>> metadataClaims =
+ new HashMap<>(entityStatement.getParsedPayload().getMetadata().getAllClaims());
+ metadataClaims.remove(entityType);
+ log.trace("Removed entity type {}", entityType);
+ final MetadataImpl metadata = new MetadataImpl(metadataClaims);
+ log.trace("Metadata contents {}", metadata);
+ if (entityStatement instanceof EntityConfiguration entityConfiguration) {
+ final EntityConfigurationPayloadImpl payload =
+ new EntityConfigurationPayloadImpl(entityConfiguration.getParsedPayload());
+ payload.setMetadata(metadata);
+ return new EntityConfigurationImpl(entityStatement.getJwt(), payload);
+ } else if (entityStatement instanceof SubordinateStatement subordinateStatement) {
+ final SubordinateStatementPayloadImpl payload =
+ new SubordinateStatementPayloadImpl(subordinateStatement.getParsedPayload());
+ payload.setMetadata(metadata);
+ return new SubordinateStatementImpl(entityStatement.getJwt(), payload);
+ }
+ return null;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultMaxPathLengthConstraint.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultMaxPathLengthConstraint.java
new file mode 100644
index 0000000..53600fd
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultMaxPathLengthConstraint.java
@@ -0,0 +1,67 @@
+/*
+ * 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.oidfed.metadata.constraints.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default implementation for the 'max_path_length' -constraint.
+ */
+public class DefaultMaxPathLengthConstraint extends AbstractFederationPolicyConstraint<Integer> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultMaxPathLengthConstraint.class);
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ protected Integer parseConstraint(@Nullable final Object constraint)
+ throws ConstraintViolationException {
+ if (constraint instanceof Integer integer) {
+ return integer;
+ } else if (constraint != null) {
+ throw new ConstraintViolationException("Unexpected value type for max_path_length: " + constraint);
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doValidate(@Nonnull final Integer constraintData,
+ @Nonnull final SubordinateStatement subordinateStatement,
+ @Nonnull final List<EntityStatement<?>> trustChain) {
+ final int length = trustChain.indexOf(subordinateStatement);
+ if (length < 1) {
+ log.error("Unexpected contents in the trust chain: subordinate statement index is {}", length);
+ }
+ log.trace("Maximum path length: {}, amount of intermediates is {}", constraintData, length - 1);
+ if (constraintData < length - 1) {
+ log.warn("Maximum path length is constricted to {}, amount of intermediates is {}",
+ constraintData, length - 1);
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultNamingConstraintsConstraint.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultNamingConstraintsConstraint.java
new file mode 100644
index 0000000..167c85b
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultNamingConstraintsConstraint.java
@@ -0,0 +1,122 @@
+/*
+ * 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.oidfed.metadata.constraints.impl;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralSubtree;
+import org.bouncycastle.asn1.x509.NameConstraintValidatorException;
+import org.bouncycastle.asn1.x509.PKIXNameConstraintValidator;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+
+/**
+ * Default implementation for the 'naming_constraints' -constraint.
+ */
+public class DefaultNamingConstraintsConstraint extends AbstractFederationPolicyConstraint<Map<String, List<String>>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultNamingConstraintsConstraint.class);
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ protected Map<String, List<String>> parseConstraint(@Nullable final Object constraint)
+ throws ConstraintViolationException {
+ if (constraint instanceof Map<?,?> map) {
+ return map.keySet().stream()
+ .filter(key -> "permitted".equals(key) || "excluded".equals(key))
+ .map(String::valueOf)
+ .collect(Collectors.toMap(key -> key, key -> parseListOfStrings(key, map.get(key))));
+ } else if (constraint != null) {
+ throw new ConstraintViolationException("Unexpected value type for naming_constraints: " + constraint);
+ }
+ return null;
+ }
+
+ /**
+ * Parses the raw object value into a list of strings.
+ *
+ * @param key the key (used solely in a potential exception)
+ * @param raw the value to be parsed
+ * @return the value as list of strings, or null if the input was null
+ * @throws ConstraintViolationException if a non-null value could not be parsed
+ */
+ @Nullable private List<String> parseListOfStrings(@Nullable final String key, @Nullable final Object raw)
+ throws ConstraintViolationException {
+ if (raw instanceof List<?> list) {
+ final List<String> result = list.stream().filter(Objects::nonNull).map(String::valueOf).toList();
+ return result;
+ } else if (raw != null) {
+ throw new ConstraintViolationException("The value for " + key + " is not a list: " + raw);
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doValidate(@Nonnull final Map<String, List<String>> constraintData,
+ @Nonnull final SubordinateStatement subordinateStatement,
+ @Nonnull final List<EntityStatement<?>> trustChain) {
+ final PKIXNameConstraintValidator bcValidator = new PKIXNameConstraintValidator();
+
+ Optional.ofNullable(constraintData.get("excluded"))
+ .orElseGet(NonnullSupplier.of(CollectionSupport.emptyList()))
+ .forEach(item -> bcValidator.addExcludedSubtree(
+ new GeneralSubtree(new GeneralName(GeneralName.dNSName, item))));
+
+ Optional.ofNullable(constraintData.get("permitted"))
+ .orElseGet(NonnullSupplier.of(CollectionSupport.emptyList()))
+ .forEach(item -> bcValidator.intersectPermittedSubtree(
+ new GeneralSubtree(new GeneralName(GeneralName.dNSName, item))));
+
+ for (final EntityStatement<?> entityStatement : trustChain) {
+ if (subordinateStatement.equals(entityStatement)) {
+ break;
+ }
+ final String host = URI.create(entityStatement.getSubject()).getHost();
+ try {
+ bcValidator.checkExcluded(new GeneralName(GeneralName.dNSName, host));
+ log.trace("Validation for 'excluded' was successful for {}", host);
+ } catch (final NameConstraintValidatorException e) {
+ log.warn("Constraint excludes entity name {}", host);
+ return false;
+ }
+ try {
+ bcValidator.checkPermitted(new GeneralName(GeneralName.dNSName, host));
+ log.trace("Validation for 'permitted' was successful for {}", host);
+ } catch (final NameConstraintValidatorException e) {
+ log.warn("Constraint does not permit entity name {}", host);
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/AbstractJWTWrapperImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/AbstractJWTWrapperImpl.java
new file mode 100644
index 0000000..9116706
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/AbstractJWTWrapperImpl.java
@@ -0,0 +1,92 @@
+/*
+ * 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.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.MoreObjects;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.BaseJWTWrapper;
+import net.shibboleth.oidfed.metadata.BasePayload;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Abstract wrapper class containing {@link SignedJWT} and its parsed payload.
+ *
+ * @param <T> payload format
+ */
+public abstract class AbstractJWTWrapperImpl<T extends BasePayload> implements BaseJWTWrapper<T> {
+
+ /** Entity statement as JWT. */
+ @Nonnull private final SignedJWT jwt;
+
+ /** Entity statement payload. */
+ @Nonnull private final T parsedPayload;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param signedJwt entity statement
+ * @param payload entity statement payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ protected AbstractJWTWrapperImpl(@Nonnull final SignedJWT signedJwt, @Nonnull final T payload)
+ throws ConstraintViolationException {
+ jwt = Constraint.isNotNull(signedJwt, "JWT cannot be null");
+ parsedPayload = Constraint.isNotNull(payload, "Entity statement payload cannot be null");
+ Constraint.isNotEmpty(parsedPayload.getIssuer(), "Issuer cannot be empty in the payload");
+ }
+
+ /**
+ * Get the entity statement as JWT.
+ *
+ * @return JWT
+ */
+ @Nonnull public SignedJWT getJwt() {
+ return jwt;
+ }
+
+ /**
+ * Get the issuer claim of the entity statement.
+ *
+ * @return issuer
+ */
+ @Nonnull @NotEmpty public String getIssuer() {
+ final String issuer = parsedPayload.getIssuer();
+ assert issuer != null;
+ return issuer;
+ }
+
+ /**
+ * Get the payload of the entity statement.
+ *
+ * @return payload
+ */
+ @Nonnull public T getParsedPayload() {
+ return parsedPayload;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("jwt", jwt.serialize())
+ .add("parsedPayload", parsedPayload)
+ .toString();
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/EntityConfigurationImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/EntityConfigurationImpl.java
new file mode 100644
index 0000000..ac6392d
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/EntityConfigurationImpl.java
@@ -0,0 +1,65 @@
+/*
+ * 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.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.payload.EntityConfigurationPayload;
+import net.shibboleth.oidfed.metadata.payload.impl.EntityConfigurationPayloadImpl;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A wrapper class extending {@link EntityStatementImpl} with parsed claims related to entity configuration.
+ */
+public class EntityConfigurationImpl extends EntityStatementImpl<EntityConfigurationPayload>
+ implements EntityConfiguration {
+
+ /**
+ * Constructor.
+ *
+ * @param signedJwt entity configuration
+ * @param payload entity configuration payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ public EntityConfigurationImpl(@Nonnull final SignedJWT signedJwt,
+ @Nonnull final EntityConfigurationPayload payload)
+ throws ConstraintViolationException {
+ super(signedJwt, payload);
+ }
+
+ /**
+ * Parse an {@link EntityConfigurationImpl} from the given JWT by using the given object mapper.
+ *
+ * @param jwt entity statement JWT
+ * @param objectMapper object mapper capable of parsing JWT payload
+ * @return entity configuration
+ * @throws JsonProcessingException if the payload could not be parsed
+ */
+ @Nonnull public static EntityConfigurationImpl parse(@Nonnull final SignedJWT jwt,
+ @Nonnull final ObjectMapper objectMapper) throws JsonProcessingException {
+ final JavaType objectType = objectMapper.constructType(EntityConfigurationPayloadImpl.class);
+ final EntityConfigurationPayloadImpl result =
+ objectMapper.readValue(jwt.getPayload().toString(), objectType);
+ assert result != null;
+ return new EntityConfigurationImpl(jwt, result);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/EntityStatementImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/EntityStatementImpl.java
new file mode 100644
index 0000000..c9daec9
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/EntityStatementImpl.java
@@ -0,0 +1,59 @@
+/*
+ * 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.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.payload.EntityStatementPayload;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A wrapper class containing the {@link SignedJWT} and parsed claims related to entity statement.
+ *
+ * @param <T> payload format
+ */
+public abstract class EntityStatementImpl<T extends EntityStatementPayload> extends AbstractJWTWrapperImpl<T>
+ implements EntityStatement<T> {
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param signedJwt entity statement
+ * @param payload entity statement payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ public EntityStatementImpl(@Nonnull final SignedJWT signedJwt, @Nonnull final T payload)
+ throws ConstraintViolationException {
+ super(signedJwt, payload);
+ Constraint.isNotEmpty(payload.getSubject(), "Subject cannot be empty in the payload");
+ }
+
+ /**
+ * Get the subject claim of the entity statement.
+ *
+ * @return subject
+ */
+ @Nonnull @NotEmpty public String getSubject() {
+ final String subject = getParsedPayload().getSubject();
+ assert subject != null;
+ return subject;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/ResolveEntityResponseImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/ResolveEntityResponseImpl.java
new file mode 100644
index 0000000..f81a857
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/ResolveEntityResponseImpl.java
@@ -0,0 +1,64 @@
+/*
+ * 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.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.ResolveEntityResponse;
+import net.shibboleth.oidfed.metadata.payload.ResolveEntityResponsePayload;
+import net.shibboleth.oidfed.metadata.payload.impl.ResolveEntityResponsePayloadImpl;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A wrapper class extending {@link EntityStatementImpl} with parsed claims related to resolve entity response.
+ */
+public class ResolveEntityResponseImpl extends AbstractJWTWrapperImpl<ResolveEntityResponsePayload>
+ implements ResolveEntityResponse {
+
+ /**
+ * Constructor.
+ *
+ * @param signedJwt entity configuration
+ * @param payload entity configuration payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ public ResolveEntityResponseImpl(@Nonnull final SignedJWT signedJwt,
+ @Nonnull final ResolveEntityResponsePayload payload)
+ throws ConstraintViolationException {
+ super(signedJwt, payload);
+ }
+
+ /**
+ * Parse an {@link ResolveEntityResponseImpl} from the given JWT by using the given object mapper.
+ *
+ * @param jwt entity statement JWT
+ * @param objectMapper object mapper capable of parsing JWT payload
+ * @return entity configuration
+ * @throws JsonProcessingException if the payload could not be parsed
+ */
+ @Nonnull public static ResolveEntityResponseImpl parse(@Nonnull final SignedJWT jwt,
+ @Nonnull final ObjectMapper objectMapper) throws JsonProcessingException {
+ final JavaType objectType = objectMapper.constructType(ResolveEntityResponsePayloadImpl.class);
+ final ResolveEntityResponsePayloadImpl result = objectMapper.readValue(jwt.getPayload().toString(), objectType);
+ assert result != null;
+ return new ResolveEntityResponseImpl(jwt, result);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/SignedKeysetImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/SignedKeysetImpl.java
new file mode 100644
index 0000000..3981687
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/SignedKeysetImpl.java
@@ -0,0 +1,63 @@
+/*
+ * 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.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.SignedKeyset;
+import net.shibboleth.oidfed.metadata.payload.SignedKeysetPayload;
+import net.shibboleth.oidfed.metadata.payload.impl.SignedKeysetPayloadImpl;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A wrapper class extending {@link EntityStatementImpl} with parsed claims related to signed keyset.
+ */
+public class SignedKeysetImpl extends AbstractJWTWrapperImpl<SignedKeysetPayload> implements SignedKeyset {
+
+ /**
+ * Constructor.
+ *
+ * @param signedJwt entity configuration
+ * @param payload entity configuration payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ public SignedKeysetImpl(@Nonnull final SignedJWT signedJwt,
+ @Nonnull final SignedKeysetPayload payload)
+ throws ConstraintViolationException {
+ super(signedJwt, payload);
+ }
+
+ /**
+ * Parse an {@link SignedKeysetImpl} from the given JWT by using the given object mapper.
+ *
+ * @param jwt entity statement JWT
+ * @param objectMapper object mapper capable of parsing JWT payload
+ * @return entity configuration
+ * @throws JsonProcessingException if the payload could not be parsed
+ */
+ @Nonnull public static SignedKeysetImpl parse(@Nonnull final SignedJWT jwt,
+ @Nonnull final ObjectMapper objectMapper) throws JsonProcessingException {
+ final JavaType objectType = objectMapper.constructType(SignedKeysetPayloadImpl.class);
+ final SignedKeysetPayloadImpl result = objectMapper.readValue(jwt.getPayload().toString(), objectType);
+ assert result != null;
+ return new SignedKeysetImpl(jwt, result);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/SubordinateStatementImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/SubordinateStatementImpl.java
new file mode 100644
index 0000000..dd7358c
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/SubordinateStatementImpl.java
@@ -0,0 +1,64 @@
+/*
+ * 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.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.payload.SubordinateStatementPayload;
+import net.shibboleth.oidfed.metadata.payload.impl.SubordinateStatementPayloadImpl;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A wrapper class extending {@link EntityStatementImpl} with parsed claims related to subordinate statements.
+ */
+public class SubordinateStatementImpl extends EntityStatementImpl<SubordinateStatementPayload>
+ implements SubordinateStatement {
+
+ /**
+ * Constructor.
+ *
+ * @param signedJwt subordinate statement
+ * @param payload subordinate statement payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ public SubordinateStatementImpl(@Nonnull final SignedJWT signedJwt,
+ @Nonnull final SubordinateStatementPayload payload)
+ throws ConstraintViolationException {
+ super(signedJwt, payload);
+ }
+
+ /**
+ * Parse a {@link SubordinateStatementImpl} from the given JWT by using the given object mapper.
+ *
+ * @param jwt entity statement JWT
+ * @param objectMapper object mapper capable of parsing JWT payload
+ * @return subordinate statement
+ * @throws JsonProcessingException if the payload could not be parsed
+ */
+ @Nonnull public static SubordinateStatementImpl parse(@Nonnull final SignedJWT jwt,
+ @Nonnull final ObjectMapper objectMapper) throws JsonProcessingException {
+ final JavaType objectType = objectMapper.constructType(SubordinateStatementPayloadImpl.class);
+ final SubordinateStatementPayloadImpl result =
+ objectMapper.readValue(jwt.getPayload().toString(), objectType);
+ assert result != null;
+ return new SubordinateStatementImpl(jwt, result);
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/TrustMarkImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/TrustMarkImpl.java
new file mode 100644
index 0000000..e4b8bc6
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/TrustMarkImpl.java
@@ -0,0 +1,63 @@
+/*
+ * 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.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.TrustMark;
+import net.shibboleth.oidfed.metadata.payload.TrustMarkPayload;
+import net.shibboleth.oidfed.metadata.payload.impl.TrustMarkPayloadImpl;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A wrapper class extending {@link EntityStatementImpl} with parsed claims related to trust mark.
+ */
+public class TrustMarkImpl extends AbstractJWTWrapperImpl<TrustMarkPayload> implements TrustMark {
+
+ /**
+ * Constructor.
+ *
+ * @param signedJwt entity configuration
+ * @param payload entity configuration payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ public TrustMarkImpl(@Nonnull final SignedJWT signedJwt,
+ @Nonnull final TrustMarkPayload payload)
+ throws ConstraintViolationException {
+ super(signedJwt, payload);
+ }
+
+ /**
+ * Parse an {@link TrustMarkImpl} from the given JWT by using the given object mapper.
+ *
+ * @param jwt entity statement JWT
+ * @param objectMapper object mapper capable of parsing JWT payload
+ * @return entity configuration
+ * @throws JsonProcessingException if the payload could not be parsed
+ */
+ @Nonnull public static TrustMarkImpl parse(@Nonnull final SignedJWT jwt,
+ @Nonnull final ObjectMapper objectMapper) throws JsonProcessingException {
+ final JavaType objectType = objectMapper.constructType(TrustMarkPayloadImpl.class);
+ final TrustMarkPayloadImpl result = objectMapper.readValue(jwt.getPayload().toString(), objectType);
+ assert result != null;
+ return new TrustMarkImpl(jwt, result);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/TrustMarkStatusImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/TrustMarkStatusImpl.java
new file mode 100644
index 0000000..a6d8ea3
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/impl/TrustMarkStatusImpl.java
@@ -0,0 +1,64 @@
+/*
+ * 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.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.TrustMarkStatus;
+import net.shibboleth.oidfed.metadata.payload.TrustMarkStatusPayload;
+import net.shibboleth.oidfed.metadata.payload.impl.TrustMarkStatusPayloadImpl;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A wrapper class extending {@link EntityStatementImpl} with parsed claims related to trust mark status.
+ */
+public class TrustMarkStatusImpl extends AbstractJWTWrapperImpl<TrustMarkStatusPayload>
+ implements TrustMarkStatus {
+
+ /**
+ * Constructor.
+ *
+ * @param signedJwt entity configuration
+ * @param payload entity configuration payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ public TrustMarkStatusImpl(@Nonnull final SignedJWT signedJwt,
+ @Nonnull final TrustMarkStatusPayload payload)
+ throws ConstraintViolationException {
+ super(signedJwt, payload);
+ }
+
+ /**
+ * Parse an {@link TrustMarkStatusImpl} from the given JWT by using the given object mapper.
+ *
+ * @param jwt entity statement JWT
+ * @param objectMapper object mapper capable of parsing JWT payload
+ * @return entity configuration
+ * @throws JsonProcessingException if the payload could not be parsed
+ */
+ @Nonnull public static TrustMarkStatusImpl parse(@Nonnull final SignedJWT jwt,
+ @Nonnull final ObjectMapper objectMapper) throws JsonProcessingException {
+ final JavaType objectType = objectMapper.constructType(TrustMarkStatusPayloadImpl.class);
+ final TrustMarkStatusPayloadImpl result = objectMapper.readValue(jwt.getPayload().toString(), objectType);
+ assert result != null;
+ return new TrustMarkStatusImpl(jwt, result);
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/InstantDeserializer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/InstantDeserializer.java
new file mode 100644
index 0000000..5cb0a42
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/InstantDeserializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.oidfed.metadata.jackson;
+
+import java.io.IOException;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+/**
+ * Jackson deserializer for {@link Instant} that exploits {@link Instant#ofEpochSecond(long)}.
+ */
+public class InstantDeserializer extends JsonDeserializer<Instant> {
+
+ /**
+ * Constructor.
+ */
+ public InstantDeserializer() {
+ // no op
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public Instant deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext)
+ throws IOException, JsonProcessingException {
+
+ final JavaType longType = TypeFactory.defaultInstance().constructType(Long.class);
+ final Long longValue = deserializationContext.readValue(jsonParser, longType);
+ final Instant result = Instant.ofEpochSecond(longValue);
+ assert result != null;
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/JWKDeserializer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/JWKDeserializer.java
new file mode 100644
index 0000000..4f10037
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/JWKDeserializer.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.oidfed.metadata.jackson;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.nimbusds.jose.jwk.JWK;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Jackson deserializer for {@link JWK} that exploits {@link JWK#parse(Map)}.
+ */
+public class JWKDeserializer extends JsonDeserializer<JWK> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(JWKDeserializer.class);
+
+ /**
+ * Constructor.
+ */
+ public JWKDeserializer() {
+ // no op
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public JWK deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext)
+ throws IOException, JsonProcessingException {
+
+ log.debug("Starting deseralize");
+ final JavaType objectType = TypeFactory.defaultInstance().constructType(Object.class);
+ final JavaType stringType = TypeFactory.defaultInstance().constructType(String.class);
+ final MapType objectMapType =
+ TypeFactory.defaultInstance().constructMapType(Map.class, stringType, objectType);
+
+ final Map<String,Object> map = deserializationContext.readValue(jsonParser, objectMapType);
+ try {
+ final JWK result = JWK.parse(map);
+ assert result != null;
+ return result;
+ } catch (final ParseException e) {
+ throw new IOException("Could not parse JWK from the given input", e);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/JWKSetDeserializer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/JWKSetDeserializer.java
new file mode 100644
index 0000000..a3e6248
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/JWKSetDeserializer.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.oidfed.metadata.jackson;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Jackson deserializer for {@link JWKSet} that exploits {@link JWKSet#parse(Map)}.
+ */
+public class JWKSetDeserializer extends JsonDeserializer<JWKSet> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(JWKSetDeserializer.class);
+
+ /**
+ * Constructor.
+ */
+ public JWKSetDeserializer() {
+ // no op
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public JWKSet deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext)
+ throws IOException, JsonProcessingException {
+
+ log.debug("Starting deseralize");
+ final JavaType objectType = TypeFactory.defaultInstance().constructType(Object.class);
+ final JavaType stringType = TypeFactory.defaultInstance().constructType(String.class);
+ final MapType objectMapType =
+ TypeFactory.defaultInstance().constructMapType(Map.class, stringType, objectType);
+
+ final Map<String,Object> map = deserializationContext.readValue(jsonParser, objectMapType);
+ try {
+ final JWKSet result = JWKSet.parse(map);
+ assert result != null;
+ return result;
+ } catch (final ParseException e) {
+ throw new IOException("Could not parse JWKSet from the given input", e);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/LocalKeyContainerDeserializer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/LocalKeyContainerDeserializer.java
new file mode 100644
index 0000000..dce9ed8
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/jackson/LocalKeyContainerDeserializer.java
@@ -0,0 +1,58 @@
+/*
+ * 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.oidfed.metadata.jackson;
+
+import java.io.IOException;
+import java.text.ParseException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JacksonException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.oidfed.metadata.cache.local.LocalKeyContainer;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Jackson deserializer for {@link LocalKeyContainer}.
+ */
+public class LocalKeyContainerDeserializer extends JsonDeserializer<LocalKeyContainer> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(LocalKeyContainerDeserializer.class);
+
+ /** {@inheritDoc} */
+ @Override
+ public LocalKeyContainer deserialize(@Nullable final JsonParser jsonParser,
+ @Nullable final DeserializationContext deserializationContext)
+ throws IOException, JacksonException {
+ assert jsonParser != null;
+ final String raw = jsonParser.readValueAsTree().toString();
+ log.trace("Raw string to be parsed into JWTSet: {}", raw);
+ try {
+ final LocalKeyContainer keyContainer = new LocalKeyContainer();
+ keyContainer.setJWKSet(JWKSet.parse(raw));
+ return keyContainer;
+ } catch (final ParseException e) {
+ throw new IOException("Could not parse JWKSet", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/impl/MetadataImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/impl/MetadataImpl.java
new file mode 100644
index 0000000..4a0e975
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/impl/MetadataImpl.java
@@ -0,0 +1,293 @@
+/*
+ * 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.oidfed.metadata.payload.claim.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidfed.metadata.payload.claim.Metadata;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Metadata claim as defined by the OpenID Federation 1.0 Section 5.
+ */
+public class MetadataImpl implements Metadata {
+
+ /** Federation entity metadata. */
+ @JsonProperty("federation_entity") private Map<String,Object> federationEntityMetadata;
+
+ /** OpenID Connect Relying Party metadata. */
+ @JsonProperty("openid_relying_party") private Map<String,Object> openidRelyingPartyMetadata;
+
+ /** OpenID Connect OpenID Provider metadata. */
+ @JsonProperty("openid_provider") private Map<String,Object> openidProviderMetadata;
+
+ /** OAuth Authorization Server metadata. */
+ @JsonProperty("oauth_authorization_server") private Map<String,Object> oauthAuthorizationServerMetadata;
+
+ /** OAuth Client metadata. */
+ @JsonProperty("oauth_client") private Map<String,Object> oauthClientMetadata;
+
+ /** OAuth Protected Resource metadata. */
+ @JsonProperty("oauth_resource") private Map<String,Object> oauthResourceMetadata;
+
+ /** The map of any other claims not directly mapped. */
+ private final Map<String, Map<String, Object>> customClaims = new HashMap<>();
+
+ /**
+ * Constructor.
+ */
+ public MetadataImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param allClaims claims used for populating this object
+ */
+ public MetadataImpl(@Nonnull final Map<String, Map<String, Object>> allClaims) {
+ for (final String claim : allClaims.keySet()) {
+ verifyNoNullValues(allClaims.get(claim), Constraint.isNotNull(claim,
+ "Metadata for entity type " + claim + " contains a null claim key"));
+ }
+ final Map<String,Map<String,Object>> input = new HashMap<>(allClaims);
+ if (allClaims.containsKey("federation_entity")) {
+ federationEntityMetadata = allClaims.get("federation_entity");
+ input.remove("federation_entity");
+ }
+ if (allClaims.containsKey("openid_relying_party")) {
+ openidRelyingPartyMetadata = allClaims.get("openid_relying_party");
+ input.remove("openid_relying_party");
+ }
+ if (allClaims.containsKey("openid_provider")) {
+ openidProviderMetadata = allClaims.get("openid_provider");
+ input.remove("openid_provider");
+ }
+ if (allClaims.containsKey("oauth_authorization_server")) {
+ oauthAuthorizationServerMetadata = allClaims.get("oauth_authorization_server");
+ input.remove("oauth_authorization_server");
+ }
+ if (allClaims.containsKey("oauth_client")) {
+ oauthClientMetadata = allClaims.get("oauth_client");
+ input.remove("oauth_client");
+ }
+ if (allClaims.containsKey("oauth_resource")) {
+ oauthResourceMetadata = allClaims.get("oauth_resource");
+ input.remove("oauth_resource");
+ }
+ customClaims.putAll(input);
+ }
+
+ /**
+ * Get the federation entity metadata.
+ *
+ * @return federation entity metadata
+ */
+ @Nullable public Map<String,Object> getFederationEntityMetadata() {
+ return federationEntityMetadata;
+ }
+
+ /**
+ * Set the federation entity metadata.
+ *
+ * @param metadata federation entity metadata
+ */
+ public void setFederationEntityMetadata(@Nullable final Map<String,Object> metadata) {
+ federationEntityMetadata = verifyNoNullValues(metadata, "federation_entity");
+ }
+
+ /**
+ * Get the OpenID Connect Relying Party metadata.
+ *
+ * @return OIDC RP metadata
+ */
+ @Nullable public Map<String,Object> getOpenidRelyingPartyMetadata() {
+ return openidRelyingPartyMetadata;
+ }
+
+ /**
+ * Set the OpenID Connect Relying Party metadata.
+ *
+ * @param metadata OIDC RP metadata
+ */
+ public void setOpenidRelyingPartyMetadata(@Nullable final Map<String,Object> metadata) {
+ openidRelyingPartyMetadata = verifyNoNullValues(metadata, "openid_relying_party");
+ }
+
+ /**
+ * Get the OpenID Connect OpenID Provider metadata.
+ *
+ * @return OIDC OP metadata
+ */
+ @Nullable public Map<String,Object> getOpenidProviderMetadata() {
+ return openidProviderMetadata;
+ }
+
+ /**
+ * Set the OpenID Connect OpenID Provider metadata.
+ *
+ * @param metadata OIDC OP metadata
+ */
+ public void setOpenidProviderMetadata(@Nullable final Map<String,Object> metadata) {
+ openidProviderMetadata = verifyNoNullValues(metadata, "openid_provider");
+ }
+
+ /**
+ * Get the OAuth Authorization Server metadata.
+ *
+ * @return OAuth AS metadata
+ */
+ @Nullable public Map<String,Object> getOauthAuthorizationServerMetadata() {
+ return oauthAuthorizationServerMetadata;
+ }
+
+ /**
+ * Set the OAuth Authorization Server metadata.
+ *
+ * @param metadata OAuth AS metadata
+ */
+ public void setOauthAuthorizationServerMetadata(@Nullable final Map<String,Object> metadata) {
+ oauthAuthorizationServerMetadata = verifyNoNullValues(metadata, "oauth_authorization_server");
+ }
+
+ /**
+ * Get the OAuth Client metadata.
+ *
+ * @return OAuth client metadata
+ */
+ @Nullable public Map<String,Object> getOauthClientMetadata() {
+ return oauthClientMetadata;
+ }
+
+ /**
+ * Set the OAuth Client metadata.
+ *
+ * @param metadata OAuth client metadata
+ */
+ public void setOauthClientMetadata(@Nullable final Map<String,Object> metadata) {
+ oauthClientMetadata = verifyNoNullValues(metadata, "oauth_client");
+ }
+
+ /**
+ * Get the OAuth Protected Resource metadata.
+ *
+ * @return OAuth protected resource metadata
+ */
+ @Nullable public Map<String,Object> getOauthResourceMetadata() {
+ return oauthClientMetadata;
+ }
+
+ /**
+ * Set the OAuth Protected Resource metadata.
+ *
+ * @param metadata OAuth protected resource metadata
+ */
+ public void setOauthResourceMetadata(@Nullable final Map<String,Object> metadata) {
+ oauthClientMetadata = verifyNoNullValues(metadata, "oauth_resource");
+ }
+
+ /**
+ * Get the map of custom claims.
+ *
+ * @return The map of any other claims not directly mapped.
+ */
+ @JsonAnyGetter
+ public Map<String,Map<String, Object>> getCustomClaims() {
+ return customClaims;
+ }
+
+ /**
+ * Add a custom claim to the map of custom claims.
+ *
+ * @param name The name of the custom claim.
+ * @param value The value of the custom claim.
+ */
+ @JsonAnySetter
+ public void setCustomClaims(final String name, final Map<String,Object> value) {
+ customClaims.put(name, Constraint.isNotNull(value, "Metadata entity type " + name + " cannot be null"));
+ }
+
+ /**
+ * Get all claims (standard and custom) in a map keyed by entity types.
+ *
+ * @return all claims
+ */
+ @Nonnull public Map<String,Map<String,Object>> getAllClaims() {
+ final Map<String,Map<String,Object>> claims = new HashMap<>();
+ if (federationEntityMetadata != null) {
+ claims.put("federation_entity", federationEntityMetadata);
+ }
+ if (openidRelyingPartyMetadata != null) {
+ claims.put("openid_relying_party", openidRelyingPartyMetadata);
+ }
+ if (openidProviderMetadata != null) {
+ claims.put("openid_provider", openidProviderMetadata);
+ }
+ if (oauthAuthorizationServerMetadata != null) {
+ claims.put("oauth_authorization_server", oauthAuthorizationServerMetadata);
+ }
+ if (oauthClientMetadata != null) {
+ claims.put("oauth_client", oauthClientMetadata);
+ }
+ if (oauthResourceMetadata != null) {
+ claims.put("oauth_resource", oauthClientMetadata);
+ }
+ claims.putAll(getCustomClaims());
+ return CollectionSupport.copyToMap(claims);
+ }
+
+ /**
+ * Verifies the given map meets syntax requirements: is not null and does not have null claim values.
+ *
+ * @param map the map to be verified
+ * @param entityType the entity type
+ * @return verified map
+ * @throws ConstraintViolationException if the map does not meet the requirements
+ */
+ protected Map<String,Object> verifyNoNullValues(@Nullable final Map<String,Object> map,
+ @Nonnull final String entityType) throws ConstraintViolationException {
+ Constraint.isNotNull(map, "Metadata entity type " + entityType + " cannot be null");
+ assert map != null;
+ for (final String claim : map.keySet()) {
+ Constraint.isNotNull(map.get(claim), "Metadata for entity type " + entityType
+ + " contains a claim with null value: " + claim);
+ }
+ return map;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("federationEntityMetadata", getFederationEntityMetadata())
+ .add("openidRelyingPartyMetadata", getOpenidRelyingPartyMetadata())
+ .add("openidProviderMetadata", getOpenidProviderMetadata())
+ .add("oauthAuthorizationServerMetadata", getOauthAuthorizationServerMetadata())
+ .add("oauthClientMetadata", getOauthClientMetadata())
+ .add("oauthResourceMetadata", getOauthResourceMetadata())
+ .add("customClaims", getCustomClaims()).toString();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/impl/TrustMarkOwnerImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/impl/TrustMarkOwnerImpl.java
new file mode 100644
index 0000000..1fddeb3
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/claim/impl/TrustMarkOwnerImpl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.oidfed.metadata.payload.claim.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.oidfed.metadata.payload.claim.TrustMarkOwner;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Trust mark owner claim to be used with a map of trust_mark_owners as defined by the OpenID Federation 1.0 Section
+ * 3.1.2.
+ */
+public class TrustMarkOwnerImpl implements TrustMarkOwner {
+
+ /** Subject. */
+ @JsonProperty("sub") private String sub;
+
+ /** A JSON Web Key Set representing the public part of the owner's Federation Entity signing keys. */
+ @JsonProperty("jwks") private JWKSet jwks;
+
+ /** The map of any other claims not directly mapped. */
+ private final Map<String, Object> customClaims = new HashMap<>();
+
+ /**
+ * Constructor.
+ */
+ public TrustMarkOwnerImpl() {
+ // no op
+ }
+
+ /**
+ * Get the subject.
+ *
+ * @return subject
+ */
+ @Nullable public String getSub() {
+ return sub;
+ }
+
+ /**
+ * Set the subject.
+ *
+ * @param subject subject
+ */
+ public void setSub(@Nullable final String subject) {
+ sub = subject;
+ }
+
+ /**
+ * Get the map of custom claims.
+ *
+ * @return The map of any other claims not directly mapped.
+ */
+ @JsonAnyGetter
+ public Map<String, Object> getCustomClaims() {
+ return customClaims;
+ }
+
+ /**
+ * Get the JWK set.
+ *
+ * @return JWK set
+ */
+ public JWKSet getJwks() {
+ return jwks;
+ }
+
+ /**
+ * Set the JWK set.
+ *
+ * @param jwkSet JWK set
+ */
+ public void setJwks(final JWKSet jwkSet) {
+ jwks = jwkSet;
+ }
+
+ /**
+ * Add a custom claim to the map of custom claims.
+ *
+ * @param name The name of the custom claim.
+ * @param value The value of the custom claim.
+ */
+ @JsonAnySetter
+ public void setCustomClaims(@Nonnull @NotEmpty final String name, @Nullable final Object value) {
+ customClaims.put(Constraint.isNotEmpty(name, "Claim name cannot be null"), value);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("sub", getSub())
+ .add("jwks", getJwks())
+ .add("customClaims", getCustomClaims()).toString();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BaseExpirableSubjectPayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BaseExpirableSubjectPayloadImpl.java
new file mode 100644
index 0000000..fce560d
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BaseExpirableSubjectPayloadImpl.java
@@ -0,0 +1,102 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidfed.metadata.payload.BaseExpirableSubjectPayload;
+
+/**
+ * Base payload carrying subject and expiration.
+ */
+public class BaseExpirableSubjectPayloadImpl extends BasePayloadImpl implements BaseExpirableSubjectPayload {
+
+ /** The Entity Identifier of the subject. */
+ @JsonProperty("sub") private String subject;
+
+ /** Expiration time after which this statement MUST NOT be accepted for processing. */
+ @JsonProperty("exp") private Instant expiration;
+
+ /**
+ * Constructor.
+ */
+ public BaseExpirableSubjectPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public BaseExpirableSubjectPayloadImpl(@Nonnull final BaseExpirableSubjectPayload payload) {
+ super(payload);
+ setSubject(payload.getSubject());
+ setExpiration(payload.getExpiration());
+ }
+
+ /**
+ * Get the subject.
+ *
+ * @return subject
+ */
+ public String getSubject() {
+ return subject;
+ }
+
+ /**
+ * Set the subject.
+ *
+ * @param sub subject to set.
+ */
+ public void setSubject(final String sub) {
+ subject = sub;
+ }
+
+ /**
+ * Get the expiration time.
+ *
+ * @return expiration time
+ */
+ public Instant getExpiration() {
+ return expiration;
+ }
+
+ /**
+ * Set the expiration time.
+ *
+ * @param exp expiration time
+ */
+ public void setExpiration(final Instant exp) {
+ expiration = exp;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("iat", getIssuedAt())
+ .add("sub", subject)
+ .add("exp", expiration).toString();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BaseMetadataPayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BaseMetadataPayloadImpl.java
new file mode 100644
index 0000000..9e171f5
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BaseMetadataPayloadImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import net.shibboleth.oidfed.metadata.payload.BaseMetadataPayload;
+import net.shibboleth.oidfed.metadata.payload.claim.Metadata;
+
+/**
+ * Base payload carrying metadata claim.
+ */
+public class BaseMetadataPayloadImpl extends BaseExpirableSubjectPayloadImpl implements BaseMetadataPayload {
+
+ /** A JSON object that declares roles that the Entity plays per its Entity Types. */
+ @JsonProperty("metadata") private Metadata metadata;
+
+ /**
+ * Constructor.
+ */
+ public BaseMetadataPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public BaseMetadataPayloadImpl(@Nonnull final BaseMetadataPayload payload) {
+ super(payload);
+ setMetadata(payload.getMetadata());
+ }
+
+ /**
+ * Get the metadata.
+ *
+ * @return metadata
+ */
+ public Metadata getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * Set the metadata.
+ *
+ * @param data metadata
+ */
+ public void setMetadata(final Metadata data) {
+ metadata = data;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BasePayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BasePayloadImpl.java
new file mode 100644
index 0000000..b5fe167
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/BasePayloadImpl.java
@@ -0,0 +1,131 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidfed.metadata.BasePayload;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Base payload for claims used within all payloads.
+ */
+public class BasePayloadImpl implements BasePayload {
+
+ /** The Entity Identifier of the Entity Statement. */
+ @JsonProperty("iss") private String issuer;
+
+ /** Time when this statement was issued. */
+ @JsonProperty("iat") private Instant issuedAt;
+
+ /** The map of any other claims not directly mapped. */
+ private final Map<String, Object> customClaims = new HashMap<>();
+
+ /**
+ * Constructor.
+ */
+ public BasePayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public BasePayloadImpl(final BasePayload payload) {
+ Constraint.isNotNull(payload, "Payload cannot be null");
+ setIssuer(payload.getIssuer());
+ setIssuedAt(payload.getIssuedAt());
+ for (final String customClaim : payload.getCustomClaims().keySet()) {
+ setCustomClaims(customClaim, payload.getCustomClaims().get(customClaim));
+ }
+ }
+
+ /**
+ * Get the issuer.
+ *
+ * @return issuer
+ */
+ public String getIssuer() {
+ return issuer;
+ }
+
+ /**
+ * Set the issuer.
+ *
+ * @param iss issuer to set
+ */
+ public void setIssuer(final String iss) {
+ issuer = iss;
+ }
+
+ /**
+ * Get the issuance time.
+ *
+ * @return issuance time
+ */
+ public Instant getIssuedAt() {
+ return issuedAt;
+ }
+
+ /**
+ * Set the issuance time.
+ *
+ * @param iat issuance time
+ */
+ public void setIssuedAt(final Instant iat) {
+ issuedAt = iat;
+ }
+
+ /**
+ * Get the map of custom claims.
+ *
+ * @return The map of any other claims not directly mapped.
+ */
+ @JsonAnyGetter
+ public Map<String, Object> getCustomClaims() {
+ return customClaims;
+ }
+
+ /**
+ * Add a custom claim to the map of custom claims.
+ *
+ * @param name The name of the custom claim.
+ * @param value The value of the custom claim.
+ */
+ @JsonAnySetter
+ public void setCustomClaims(final String name, final Object value) {
+ customClaims.put(name, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", issuer)
+ .add("iat", issuedAt)
+ .add("customClaims", customClaims).toString();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/EntityConfigurationPayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/EntityConfigurationPayloadImpl.java
new file mode 100644
index 0000000..4a14afb
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/EntityConfigurationPayloadImpl.java
@@ -0,0 +1,186 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidfed.metadata.payload.EntityConfigurationPayload;
+import net.shibboleth.oidfed.metadata.payload.claim.TrustMarkOwner;
+
+/**
+ * Entity Configuration payload claims as defined by the OpenID Federation 1.0 Section 3.2. This class extends the
+ * list of claims defined by {@link EntityStatementPayloadImpl} with the claims that MUST or MAY appear in Entity
+ * Configurations but not in Subordinate Statements.
+ */
+public class EntityConfigurationPayloadImpl extends EntityStatementPayloadImpl implements EntityConfigurationPayload {
+
+ /** An array of strings representing the Entity Identifiers of Immediate Superiors. */
+ @JsonProperty("authority_hints") private List<String> authorityHints;
+
+ /** An array of strings representing the Entity Identifiers of Trust Anchors trusted by the Entity. */
+ @JsonProperty("trust_anchor_hints") private List<String> trustAnchorHints;
+
+ /** An array of Trust Marks. */
+ @JsonProperty("trust_marks") private List<Map<String,String>> trustMarks;
+
+ /**
+ * A map of arrays of Entity Identifiers that are trusted to represent the accreditation authority for Trust Marks.
+ */
+ @JsonProperty("trust_mark_issuers") private Map<String, List<String>> trustMarkIssuers;
+
+ /** A map of Owners of the Trust Marks. */
+ @JsonProperty("trust_mark_owners") private Map<String, TrustMarkOwner> trustMarkOwners;
+
+ /**
+ * Constructor.
+ */
+ public EntityConfigurationPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public EntityConfigurationPayloadImpl(@Nonnull final EntityConfigurationPayload payload) {
+ super(payload);
+ setAuthorityHints(payload.getAuthorityHints());
+ setTrustAnchorHints(payload.getTrustAnchorHints());
+ setTrustMarks(payload.getTrustMarks());
+ setTrustMarkIssuers(payload.getTrustMarkIssuers());
+ setTrustMarkOwners(payload.getTrustMarkOwners());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("sub", getSubject())
+ .add("iat", getIssuedAt())
+ .add("exp", getExpiration())
+ .add("jwks", getJwks())
+ .add("metadata", getMetadata())
+ .add("crit", getCritical())
+ .add("authority_hints", authorityHints)
+ .add("trust_anchor_hints", trustAnchorHints)
+ .add("trust_marks", trustMarks)
+ .add("trust_mark_issuers", trustMarkIssuers)
+ .add("trust_mark_owners", trustMarkOwners)
+ .add("customClaims", getCustomClaims()).toString();
+ }
+
+ /**
+ * Get the array of strings representing the Entity Identifiers of Immediate Superiors.
+ *
+ * @return authority hints
+ */
+ @Nullable public List<String> getAuthorityHints() {
+ return authorityHints;
+ }
+
+ /**
+ * Set the array of strings representing the Entity Identifiers of Immediate Superiors.
+ *
+ * @param hints authority hints
+ */
+ public void setAuthorityHints(@Nullable List<String> hints) {
+ authorityHints = hints;
+ }
+
+ /**
+ * Get the array of strings representing the Entity Identifiers of Trust Anchors trusted by the Entity.
+ *
+ * @return trust anchor hints
+ */
+ @Nullable public List<String> getTrustAnchorHints() {
+ return trustAnchorHints;
+ }
+
+ /**
+ * Set the array of strings representing the Entity Identifiers of Trust Anchors trusted by the Entity.
+ *
+ * @param hints trust anchor hints
+ */
+ public void setTrustAnchorHints(@Nullable List<String> hints) {
+ trustAnchorHints = hints;
+ }
+
+ /**
+ * Get the array of Trust Marks.
+ *
+ * @return trust marks
+ */
+ @Nullable public List<Map<String, String>> getTrustMarks() {
+ return trustMarks;
+ }
+
+ /**
+ * Set the array of Trust Marks.
+ *
+ * @param marks trust marks
+ */
+ public void setTrustMarks(@Nullable List<Map<String, String>> marks) {
+ trustMarks = marks;
+ }
+
+ /**
+ * Get the map of arrays of Entity Identifiers that are trusted to represent the accreditation authority for Trust
+ * Marks.
+ *
+ * @return trust mark issuers
+ */
+ @Nullable public Map<String, List<String>> getTrustMarkIssuers() {
+ return trustMarkIssuers;
+ }
+
+ /**
+ * Set the map of arrays of Entity Identifiers that are trusted to represent the accreditation authority for Trust
+ * Marks.
+ *
+ * @param issuers trust mark issuers
+ */
+ public void setTrustMarkIssuers(@Nullable Map<String, List<String>> issuers) {
+ trustMarkIssuers = issuers;
+ }
+
+ /**
+ * Get the map of Owners of the Trust Marks.
+ *
+ * @return trust mark owners
+ */
+ @Nullable public Map<String, TrustMarkOwner> getTrustMarkOwners() {
+ return trustMarkOwners;
+ }
+
+ /**
+ * Set the map of Owners of the Trust Marks.
+ *
+ * @param owners trust mark owners
+ */
+ public void setTrustMarkOwners(@Nullable Map<String, TrustMarkOwner> owners) {
+ trustMarkOwners = owners;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/EntityStatementPayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/EntityStatementPayloadImpl.java
new file mode 100644
index 0000000..a3c56b7
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/EntityStatementPayloadImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.oidfed.metadata.payload.EntityStatementPayload;
+
+/**
+ * Entity Statement payload claims as defined by the OpenID Federation 1.0 Section 3.1. These claims MUST or MAY appear
+ * in both Entity Configurations and Subordinate Statements.
+ */
+public class EntityStatementPayloadImpl extends BaseMetadataPayloadImpl implements EntityStatementPayload {
+
+ /** A JSON Web Key Set representing the public part of the subject's Federation Entity signing keys. */
+ @JsonProperty("jwks") private JWKSet jwkSet;
+
+ /** The critical Claims that must be understood and processed. */
+ @JsonProperty("crit") private List<String> critical;
+
+ /**
+ * Constructor.
+ */
+ public EntityStatementPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public EntityStatementPayloadImpl(@Nonnull final EntityStatementPayload payload) {
+ super(payload);
+ setJwks(payload.getJwks());
+ setCritical(payload.getCritical());
+ }
+
+ /**
+ * Get the JWK set.
+ *
+ * @return JWK set
+ */
+ public JWKSet getJwks() {
+ return jwkSet;
+ }
+
+ /**
+ * Set the JWK set.
+ *
+ * @param jwks JWK set
+ */
+ public void setJwks(final JWKSet jwks) {
+ jwkSet = jwks;
+ }
+
+ /**
+ * Get the critical claims.
+ *
+ * @return critical claims
+ */
+ public List<String> getCritical() {
+ return critical;
+ }
+
+ /**
+ * Set the critical claims.
+ *
+ * @param crit critical claims
+ */
+ public void setCritical(final List<String> crit) {
+ critical = crit;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("sub", getSubject())
+ .add("iat", getIssuedAt())
+ .add("exp", getExpiration())
+ .add("jwks", jwkSet)
+ .add("metadata", getMetadata())
+ .add("crit", critical)
+ .add("customClaims", getCustomClaims()).toString();
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/ResolveEntityResponsePayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/ResolveEntityResponsePayloadImpl.java
new file mode 100644
index 0000000..cdc4549
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/ResolveEntityResponsePayloadImpl.java
@@ -0,0 +1,106 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidfed.metadata.payload.ResolveEntityResponsePayload;
+
+/**
+ * Resolve Entity response payload claims as defined by the OpenID Federation 1.0 Section 8.3.2.
+ */
+public class ResolveEntityResponsePayloadImpl extends BaseMetadataPayloadImpl implements ResolveEntityResponsePayload {
+
+ /** An array containing the sequence of Entity Statements that compose the Trust Chain. */
+ @JsonProperty("trust_chain") private List<String> trustChain;
+
+ /** An array of Trust Marks. */
+ @JsonProperty("trust_marks") private List<Map<String,String>> trustMarks;
+
+ /**
+ * Constructor.
+ */
+ public ResolveEntityResponsePayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public ResolveEntityResponsePayloadImpl(@Nonnull final ResolveEntityResponsePayload payload) {
+ super(payload);
+ setTrustChain(payload.getTrustChain());
+ setTrustMarks(payload.getTrustMarks());
+ }
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("sub", getSubject())
+ .add("iat", getIssuedAt())
+ .add("exp", getExpiration())
+ .add("metadata", getMetadata())
+ .add("trust_chain", getTrustChain())
+ .add("trust_marks", getTrustMarks())
+ .add("customClaims", getCustomClaims()).toString();
+ }
+
+ /**
+ * Get the array containing the sequence of Entity Statements that compose the Trust Chain.
+ *
+ * @return trust chain
+ */
+ @Nullable public List<String> getTrustChain() {
+ return trustChain;
+ }
+
+ /**
+ * Set the array containing the sequence of Entity Statements that compose the Trust Chain.
+ *
+ * @param chain trust chain
+ */
+ public void setTrustChain(final @Nullable List<String> chain) {
+ trustChain = chain;
+ }
+
+ /**
+ * Get the array of Trust Marks.
+ *
+ * @return trust marks
+ */
+ @Nullable public List<Map<String, String>> getTrustMarks() {
+ return trustMarks;
+ }
+
+ /**
+ * Set the array of Trust Marks.
+ *
+ * @param marks trust marks
+ */
+ public void setTrustMarks(@Nullable List<Map<String, String>> marks) {
+ trustMarks = marks;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/SignedKeysetPayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/SignedKeysetPayloadImpl.java
new file mode 100644
index 0000000..dd5ccdc
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/SignedKeysetPayloadImpl.java
@@ -0,0 +1,82 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+import com.nimbusds.jose.jwk.JWK;
+
+import net.shibboleth.oidfed.metadata.payload.SignedKeysetPayload;
+
+/**
+ * Signed keyset payload claims as defined by the OpenID Federation 1.0 Section 5.2.1.
+ */
+public class SignedKeysetPayloadImpl extends BaseExpirableSubjectPayloadImpl implements SignedKeysetPayload {
+
+ /** A JWK set. */
+ @JsonProperty("keys") private List<JWK> keys;
+
+ /**
+ * Constructor.
+ */
+ public SignedKeysetPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public SignedKeysetPayloadImpl(@Nonnull final SignedKeysetPayload payload) {
+ super(payload);
+ setKeys(payload.getKeys());
+ }
+
+ /**
+ * Get the JWK set.
+ *
+ * @return JWK set
+ */
+ public List<JWK> getKeys() {
+ return keys;
+ }
+
+ /**
+ * Set the JWK set.
+ *
+ * @param jwks JWK set
+ */
+ public void setKeys(final List<JWK> jwks) {
+ keys = jwks;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("sub", getSubject())
+ .add("iat", getIssuedAt())
+ .add("exp", getExpiration())
+ .add("keys", keys)
+ .add("customClaims", getCustomClaims()).toString();
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/SubordinateStatementPayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/SubordinateStatementPayloadImpl.java
new file mode 100644
index 0000000..20ebb66
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/SubordinateStatementPayloadImpl.java
@@ -0,0 +1,161 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidfed.metadata.payload.SubordinateStatementPayload;
+
+/**
+ * Subordinate Statement payload claims as defined by the OpenID Federation 1.0 Section 3.3. This class extends the
+ * list of claims defined by {@link EntityStatementPayloadImpl} with the claims that MUST or MAY appear in Subordinate
+ * Statements but not in Entity Configurations.
+ */
+public class SubordinateStatementPayloadImpl extends EntityStatementPayloadImpl
+ implements SubordinateStatementPayload {
+
+ /** A map that defines Trust Chain constraints. */
+ @JsonProperty("constraints") private Map<String, Object> constraints;
+
+ /** A map that defines a metadata policy. */
+ @JsonProperty("metadata_policy") private Map<String,Map<String,MetadataPolicy>> metadataPolicy;
+
+ /** A list of critical metadata policy operators. */
+ @JsonProperty("metadata_policy_crit") private List<String> metadataPolicyCritical;
+
+ /** A string containing the fetch endpoint URL from which the entity statement was issued. */
+ @JsonProperty("source_endpoint") private String sourceEndpoint;
+
+ /**
+ * Constructor.
+ */
+ public SubordinateStatementPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public SubordinateStatementPayloadImpl(@Nonnull final SubordinateStatementPayload payload) {
+ super(payload);
+ setConstraints(payload.getConstraints());
+ setMetadataPolicy(payload.getMetadataPolicy());
+ setMetadataPolicyCritical(payload.getMetadataPolicyCritical());
+ setSourceEndpoint(payload.getSourceEndpoint());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("sub", getSubject())
+ .add("iat", getIssuedAt())
+ .add("exp", getExpiration())
+ .add("jwks", getJwks())
+ .add("metadata", getMetadata())
+ .add("crit", getCritical())
+ .add("constraints", constraints)
+ .add("metadata_policy", metadataPolicy)
+ .add("metadata_policy_crit", metadataPolicyCritical)
+ .add("source_endpoint", sourceEndpoint)
+ .add("customClaims", getCustomClaims()).toString();
+ }
+
+ /**
+ * Get the map that defines Trust Chain constraints.
+ *
+ * @return constraints
+ */
+ @Nullable public Map<String, Object> getConstraints() {
+ return constraints;
+ }
+
+ /**
+ * Set the map that defines Trust Chain constraints.
+ *
+ * @param map constraints
+ */
+ public void setConstraints(@Nullable final Map<String, Object> map) {
+ constraints = map;
+ }
+
+ /**
+ * Get the map that defines a metadata policy.
+ *
+ * @return metadata policy
+ */
+ @Nullable public Map<String, Map<String, MetadataPolicy>> getMetadataPolicy() {
+ return metadataPolicy;
+ }
+
+ /**
+ * Set the map that defines a metadata policy.
+ *
+ * @param map metadata policy
+ */
+ public void setMetadataPolicy(@Nullable final Map<String, Map<String, MetadataPolicy>> map) {
+ metadataPolicy = map;
+ }
+
+ /**
+ * Get the list of critical metadata policy operators.
+ *
+ * @return critical metadata policy operators
+ */
+ @Nullable public List<String> getMetadataPolicyCritical() {
+ return metadataPolicyCritical;
+ }
+
+ /**
+ * Set the list of critical metadata policy operators.
+ *
+ * @param operators critical metadata policy operators
+ */
+ public void setMetadataPolicyCritical(@Nullable final List<String> operators) {
+ metadataPolicyCritical = operators;
+ }
+
+ /**
+ * Get the fetch endpoint URL from which the entity statement was issued.
+ *
+ * @return fetch endpoint URL
+ */
+ @Nullable public String getSourceEndpoint() {
+ return sourceEndpoint;
+ }
+
+ /**
+ * Set the fetch endpoint URL from which the entity statement was issued.
+ *
+ * @param url endpoint URL
+ */
+ public void setSourceEndpoint(@Nullable final String url) {
+ sourceEndpoint = url;
+ }
+
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/TrustMarkPayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/TrustMarkPayloadImpl.java
new file mode 100644
index 0000000..5403cdc
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/TrustMarkPayloadImpl.java
@@ -0,0 +1,149 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidfed.metadata.payload.TrustMarkPayload;
+
+/**
+ * Trust Mark payload claims as defined by the OpenID Federation 1.0 Section 7.1.
+ */
+public class TrustMarkPayloadImpl extends BaseExpirableSubjectPayloadImpl implements TrustMarkPayload {
+
+ /** An identifier of the type of the Trust Mark. */
+ @JsonProperty("trust_mark_type") private String trustMarkType;
+
+ /** A URL that references a logo for the issued Trust Mark. */
+ @JsonProperty("logo_uri") private String logoUri;
+
+ /** A URL referring to human-readable information about the issuance of the Trust Mark. */
+ @JsonProperty("ref") private String reference;
+
+ /** A Trust Mark delegation JWT. */
+ @JsonProperty("delegation") private String delegation;
+
+ /**
+ * Constructor.
+ */
+ public TrustMarkPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public TrustMarkPayloadImpl(@Nonnull final TrustMarkPayload payload) {
+ super(payload);
+ setTrustMarkType(payload.getTrustMarkType());
+ setLogoUri(payload.getLogoUri());
+ setReference(payload.getReference());
+ setDelegation(payload.getDelegation());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("sub", getSubject())
+ .add("iat", getIssuedAt())
+ .add("exp", getExpiration())
+ .add("trust_mark_type", trustMarkType)
+ .add("logo_uri", logoUri)
+ .add("ref", reference)
+ .add("delegation", delegation)
+ .add("customClaims", getCustomClaims()).toString();
+ }
+
+ /**
+ * Get the identifier of the type of the Trust Mark.
+ *
+ * @return trust mark type
+ */
+ @Nullable public String getTrustMarkType() {
+ return trustMarkType;
+ }
+
+ /**
+ * Set the identifier of the type of the Trust Mark.
+ *
+ * @param type trust mark type
+ */
+ public void setTrustMarkType(@Nullable final String type) {
+ trustMarkType = type;
+ }
+
+ /**
+ * Get the URL that references a logo for the issued Trust Mark.
+ *
+ * @return logo URL
+ */
+ @Nullable public String getLogoUri() {
+ return logoUri;
+ }
+
+ /**
+ * Set the URL that references a logo for the issued Trust Mark.
+ *
+ * @param url logo URL
+ */
+ public void setLogoUri(@Nullable final String url) {
+ logoUri = url;
+ }
+
+ /**
+ * Get the URL referring to human-readable information about the issuance of the Trust Mark.
+ *
+ * @return reference URL
+ */
+ @Nullable public String getReference() {
+ return reference;
+ }
+
+ /**
+ * Set the URL referring to human-readable information about the issuance of the Trust Mark.
+ *
+ * @param ref reference URL
+ */
+ public void setReference(@Nullable final String ref) {
+ reference = ref;
+ }
+
+ /**
+ * Get the Trust Mark delegation JWT.
+ *
+ * @return delegation JWT
+ */
+ @Nullable public String getDelegation() {
+ return delegation;
+ }
+
+ /**
+ * Set the Trust Mark delegation JWT.
+ *
+ * @param jwt delegation JWT
+ */
+ public void setDelegation(@Nullable final String jwt) {
+ delegation = jwt;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/TrustMarkStatusPayloadImpl.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/TrustMarkStatusPayloadImpl.java
new file mode 100644
index 0000000..ab3eb0d
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/payload/impl/TrustMarkStatusPayloadImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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.oidfed.metadata.payload.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.oidfed.metadata.payload.TrustMarkStatusPayload;
+
+/**
+ * Trust Mark Status payload claims as defined by the OpenID Federation 1.0 Section 8.4.2.
+ */
+public class TrustMarkStatusPayloadImpl extends BasePayloadImpl implements TrustMarkStatusPayload {
+
+ /** A Trust Mark. */
+ @JsonProperty("trust_mark") private String trustMark;
+
+ /** A status indicator for the Trust Mark. */
+ @JsonProperty("status") private String status;
+
+ /**
+ * Constructor.
+ */
+ public TrustMarkStatusPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public TrustMarkStatusPayloadImpl(@Nonnull final TrustMarkStatusPayload payload) {
+ super(payload);
+ setTrustMark(payload.getTrustMark());
+ setStatus(payload.getStatus());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("iat", getIssuedAt())
+ .add("trust_mark", trustMark)
+ .add("status", status)
+ .add("customClaims", getCustomClaims()).toString();
+ }
+
+ /**
+ * Get the Trust Mark.
+ *
+ * @return trust mark
+ */
+ @Nullable public String getTrustMark() {
+ return trustMark;
+ }
+
+ /**
+ * Set the Trust Mark.
+ *
+ * @param mark trust mark
+ */
+ public void setTrustMark(@Nullable final String mark) {
+ trustMark = mark;
+ }
+
+ /**
+ * Get the status indicator for the Trust Mark.
+ *
+ * @return status
+ */
+ @Nullable public String getStatus() {
+ return status;
+ }
+
+ /**
+ * Set the status indicator for the Trust Mark.
+ *
+ * @param value status
+ */
+ public void setStatus(@Nullable final String value) {
+ status = value;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyDeserializer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyDeserializer.java
new file mode 100644
index 0000000..eb7d171
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyDeserializer.java
@@ -0,0 +1,159 @@
+/*
+ * 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.oidfed.metadata.policy;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default deserializer for the {@link MetadataPolicy} used within OpenID Federation. If the value for the "value"
+ * -operator is explicitly set to null in JSON, this deserializer sets {@link MetadataPolicy#setValue(Object)} into
+ * {@link Optional#empty()}.
+ */
+public class FederationMetadataPolicyDeserializer extends JsonDeserializer<MetadataPolicy> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(FederationMetadataPolicyDeserializer.class);
+
+ /** List of claim names who are transformed from a space-separated String into a List. */
+ @Nonnull private final List<String> arraysAsSpaceSeparatedList;
+
+ /**
+ * Constructor.
+ */
+ public FederationMetadataPolicyDeserializer() {
+ this("scope");
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string comma-separated list of claim names who are transformed from a space-separated String into a List.
+ */
+ public FederationMetadataPolicyDeserializer(
+ @Nonnull @ParameterName(name = "arraysAsSpaceSeparatedList") final String string) {
+ final List<String> list = Arrays.asList(Constraint.isNotEmpty(string, "The string cannot be empty").split(","));
+ assert list != null;
+ arraysAsSpaceSeparatedList = list;
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public MetadataPolicy deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext)
+ throws IOException, JsonProcessingException {
+ final MetadataPolicy policy = new MetadataPolicy();
+ log.debug("Starting deseralize");
+ final JavaType objectType = TypeFactory.defaultInstance().constructType(Object.class);
+ final JavaType stringType = TypeFactory.defaultInstance().constructType(String.class);
+ final MapType objectMapType =
+ TypeFactory.defaultInstance().constructMapType(Map.class, stringType, objectType);
+
+ final Map<String,Object> map = deserializationContext.readValue(jsonParser, objectMapType);
+ final String claim = jsonParser.getParsingContext().getCurrentName();
+ if (claim == null) {
+ throw new IOException("Could not find the parent claim name for the metadata policy");
+ }
+ log.debug("Processing claim {}, value {}", claim, map);
+
+ if (map != null) {
+ log.debug("Processing map object {}", map);
+ for (final String key : map.keySet().stream().filter(Objects::nonNull).toList()) {
+ switch (key) {
+ case "value":
+ final Object value = FederationMetadataPolicyHelper.transformSpaceSeparatedStringIntoList(
+ arraysAsSpaceSeparatedList, claim, map.get("value"));
+ policy.setValue(value != null ? value : Optional.empty());
+ break;
+ case "add":
+ policy.setAdd(FederationMetadataPolicyHelper.transformSpaceSeparatedStringIntoList(
+ arraysAsSpaceSeparatedList, claim, map.get("add")));
+ break;
+ case "default":
+ policy.setDefaultValue(FederationMetadataPolicyHelper.transformSpaceSeparatedStringIntoList(
+ arraysAsSpaceSeparatedList, claim, map.get("default")));
+ break;
+ case "essential":
+ policy.setEssential(map.get("essential") != null ?
+ Boolean.valueOf(String.valueOf(map.get("essential"))).booleanValue() : false);
+ break;
+ case "one_of":
+ policy.setOneOfValues(transformObjectIntoList("one_of",
+ FederationMetadataPolicyHelper.transformSpaceSeparatedStringIntoList(
+ arraysAsSpaceSeparatedList, claim, map.get("one_of"))));
+ break;
+ case "subset_of":
+ policy.setSubsetOfValues(transformObjectIntoList("subset_of",
+ FederationMetadataPolicyHelper.transformSpaceSeparatedStringIntoList(
+ arraysAsSpaceSeparatedList, claim, map.get("subset_of"))));
+ break;
+ case "superset_of":
+ policy.setSupersetOfValues(transformObjectIntoList("superset_of",
+ FederationMetadataPolicyHelper.transformSpaceSeparatedStringIntoList(
+ arraysAsSpaceSeparatedList, claim, map.get("superset_of"))));
+ break;
+ case "regexp":
+ policy.setRegexp(map.get("regexp") == null ? null : "" + map.get("regexp"));
+ break;
+ default:
+ policy.setCustomOperator(key, map.get(key));
+ break;
+ }
+ }
+ } else {
+ log.debug("No map object could be parsed from the input, leaving the policy empty");
+ }
+ return policy;
+ }
+
+ /**
+ * Transforms the given value into a list of objects.
+ *
+ * @param id the key for the map of objects
+ * @param object the object value
+ * @return the value for the key as list or null
+ * @throws IOException if a non-null value could not be transformed into a list
+ */
+ @Nullable private List<Object> transformObjectIntoList(@Nonnull final String id,
+ @Nullable final Object object) throws IOException {
+ if (object instanceof List<?> list) {
+ return list.stream().filter(Object.class::isInstance).map(Object.class::cast).toList();
+ } else if (object != null) {
+ throw new IOException("The value '" + object + "' for '" + id + "' is not a list");
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyHelper.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyHelper.java
new file mode 100644
index 0000000..e9d7c74
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyHelper.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.oidfed.metadata.policy;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Static utility method related to federation metadata policies.
+ */
+public class FederationMetadataPolicyHelper {
+
+ /**
+ * Transforms a list into a space-separated string. Operation is done for non-null values if the given claim is
+ * included in the given list of claims that are to be transformed.
+ *
+ * @param arraysAsSpaceSeparatedList the list of claim names to be transformed
+ * @param claim the candidate claim
+ * @param value the candidate claim value
+ * @return the claim value transformed into a space-separated string if it met the requirements, or initial value
+ * if not
+ * @throws ConstraintViolationException if the non-null value to be transformed was not a list
+ */
+ @Nullable
+ public static Object transformListIntoSpaceSeparatedString(@Nonnull final List<String> arraysAsSpaceSeparatedList,
+ @Nonnull final String claim, @Nullable final Object value) throws ConstraintViolationException {
+ if (arraysAsSpaceSeparatedList.contains(claim)) {
+ if (value instanceof List<?> list) {
+ return list.stream()
+ .map(item -> String.valueOf(item))
+ .collect(Collectors.joining(" "));
+ } else if (value != null) {
+ throw new ConstraintViolationException(
+ "Unexpected value for claim " + claim + ": the value is not a List");
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Transforms a space-separated string into a list. Operation is done for non-null values if the given claim is
+ * included in the given list of claims that are to be transformed.
+ *
+ * @param arraysAsSpaceSeparatedList the list of claim names to be transformed
+ * @param claim the candidate claim
+ * @param value the candidate claim value
+ * @return the claim value transformed into a list if it met the requirements, or initial value
+ * @throws ConstraintViolationException if the non-null value to be transformed was not a string
+ */
+ @Nullable
+ public static Object transformSpaceSeparatedStringIntoList(@Nonnull final List<String> arraysAsSpaceSeparatedList,
+ @Nonnull final String claim, @Nullable final Object value) throws ConstraintViolationException {
+ if (arraysAsSpaceSeparatedList.contains(claim)) {
+ if (value instanceof String string) {
+ return List.of(string.split(" "));
+ } else if (value != null) {
+ throw new ConstraintViolationException(
+ "Unexpected value for claim " + claim + ": the value is not a String");
+ }
+ }
+ return value;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyOperator.java
new file mode 100644
index 0000000..dceaa24
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/FederationMetadataPolicyOperator.java
@@ -0,0 +1,57 @@
+/*
+ * 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.oidfed.metadata.policy;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Interface to be implemented by the federation metadata policy operators.
+ *
+ * TODO: once moved to commons, merge with net.shibboleth.oidc.metadata.policy.impl.CustomMetadataPolicyOperator
+ */
+public interface FederationMetadataPolicyOperator {
+
+ /**
+ * The unique case-sensitive name of this operator.
+ *
+ * @return the operator name.
+ */
+ @Nonnull @NotEmpty public String getOperatorName();
+
+ /**
+ * Validate whether the operator is compatible with the other operators in the policy.
+ *
+ * @param policy The metadata policy to be used by the custom policy operator.
+ * @return true if the policy is valid for this operator, false otherwise.
+ */
+ public boolean validate(@Nonnull final MetadataPolicy policy);
+
+ /**
+ * Apply the operator for the given input that has the given metadata policy attached.
+ *
+ * @param inputValue The value to be used by the custom policy operator.
+ * @param policy The metadata policy to be used by the custom policy operator.
+ * @return The value returned by the custom policy operator.
+ * @throws ConstraintViolationException If the value-check of the custom operator fails.
+ */
+ @Nullable public Object apply(@Nullable final Object inputValue, @Nonnull final MetadataPolicy policy)
+ throws ConstraintViolationException;
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/AbstractFederationMetadataPolicyOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/AbstractFederationMetadataPolicyOperator.java
new file mode 100644
index 0000000..080e8de
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/AbstractFederationMetadataPolicyOperator.java
@@ -0,0 +1,56 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidfed.metadata.policy.FederationMetadataPolicyOperator;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Base implementation for the classes implementing {@link FederationMetadataPolicyOperator}.
+ */
+public abstract class AbstractFederationMetadataPolicyOperator extends AbstractIdentifiableInitializableComponent
+ implements FederationMetadataPolicyOperator {
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean validate(@Nonnull final MetadataPolicy policy) {
+ return true;
+ }
+
+ /**
+ * Parses a list from the given object.
+ *
+ * @param object the object to be parsed
+ * @return a list of it couöd be parsed from the object, or null if input was null
+ * @throws ConstraintViolationException thrown if non-null input value was not a list
+ */
+ @Nullable protected static List<Object> parseList(@Nullable final Object object)
+ throws ConstraintViolationException {
+ if (object instanceof List<?> list) {
+ return list.stream().filter(Object.class::isInstance).map(Object.class::cast).toList();
+ } else if (object != null) {
+ throw new ConstraintViolationException("Could not parse list from the given object");
+ }
+ return null;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyAddOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyAddOperator.java
new file mode 100644
index 0000000..54fac3d
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyAddOperator.java
@@ -0,0 +1,115 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.MetadataPolicyHelper;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default implementation for the 'add'-operator.
+ */
+public class DefaultFederationMetadataPolicyAddOperator extends AbstractFederationMetadataPolicyOperator {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultFederationMetadataPolicyAddOperator.class);
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getOperatorName() {
+ return "add";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Object apply(@Nullable final Object candidate, @Nonnull final MetadataPolicy policy)
+ throws ConstraintViolationException {
+ final List<Object> add = parseList(policy.getAdd());
+ if (add != null) {
+ if (candidate instanceof List<?> list) {
+ log.debug("Combining candidate {} and add {}", list, add);
+ return buildResult(Stream.concat(list.stream(), add.stream()));
+ } else if (candidate == null) {
+ log.debug("Returning the contents of the add operator: {}", add);
+ return buildResult(add.stream());
+ } else {
+ log.debug("Candidate value {} is not a list/array", candidate);
+ throw new ConstraintViolationException("Candidate " + candidate + " is not a list/array");
+ }
+ }
+ return candidate;
+ }
+
+ /**
+ * Builds a list from the given stream.
+ *
+ * @param stream the stream
+ * @return the list
+ */
+ @Nonnull protected List<Object> buildResult(final Stream<?> stream) {
+ final List<Object> result = stream
+ .filter(Object.class::isInstance)
+ .map(Object.class::cast)
+ .distinct()
+ .toList();
+ assert result != null;
+ return CollectionSupport.copyToList(result);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean validate(@Nonnull final MetadataPolicy policy) {
+ final List<Object> add;
+ try {
+ add = parseList(policy.getAdd());
+ } catch (final ConstraintViolationException e) {
+ log.warn("The value for add operator {} is not a list/array", policy.getAdd());
+ return false;
+ }
+ if (add == null || add.isEmpty()) {
+ return true;
+ }
+ boolean validation = true;
+ final Object value = policy.getValue();
+ if (value instanceof Collection<?> valueCollection) {
+ if (!MetadataPolicyHelper.isSubsetOfValues(add, valueCollection)) {
+ log.debug("The value for add operator {} is not a subset of value {}", add, valueCollection);
+ validation = false;
+ }
+ } else if (value != null) {
+ log.warn("The value {} is not a list/array", value);
+ validation = false;
+ }
+ final List<Object> subsetOf = policy.getSubsetOfValues();
+ if (subsetOf != null && !MetadataPolicyHelper.isSubsetOfValues(add, subsetOf)) {
+ log.debug("The value for add operator {} is not a subset of value for the subset_of operator {}", add,
+ subsetOf);
+ validation = false;
+ }
+ return validation;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyDefaultOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyDefaultOperator.java
new file mode 100644
index 0000000..c80d6ee
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyDefaultOperator.java
@@ -0,0 +1,44 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Default implementation for the 'default' -operator.
+ */
+public class DefaultFederationMetadataPolicyDefaultOperator extends AbstractFederationMetadataPolicyOperator {
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getOperatorName() {
+ return "default";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Object apply(@Nullable final Object candidate, @Nonnull final MetadataPolicy policy)
+ throws ConstraintViolationException {
+ if (candidate != null) {
+ return candidate;
+ }
+ final Object defaultValue = policy.getDefaultValue();
+ return defaultValue != null ? defaultValue : candidate;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyEnforcer.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyEnforcer.java
new file mode 100644
index 0000000..b5df4a4
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyEnforcer.java
@@ -0,0 +1,95 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidfed.metadata.policy.FederationMetadataPolicyOperator;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * <p>A function that applies the given {@link MetadataPolicy} to the given object. The input is given as a {@link
+ * Pair} of the object and the policy. The policy is applied to the incoming object as specified in the OpenID
+ * Federation specification 1.0 (draft 42 / April 2025)</p>
+ *
+ * <p>In addition to the standard operators, we also support regular expression validation.</p>
+ *
+ * <p>The function returns a {@link Pair} of the object for which the value modifiers of the metadata policy have
+ * been applied to, and a flag indicating if the object was compatible with the value checks of the metadata policy.
+ * </p>
+ */
+public class DefaultFederationMetadataPolicyEnforcer extends AbstractIdentifiableInitializableComponent
+ implements BiFunction<Object,MetadataPolicy,Pair<Object,Boolean>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultFederationMetadataPolicyEnforcer.class);
+
+ /** The list of metadata policy operators used for enforcing the metadata policy. */
+ @NonnullAfterInit List<FederationMetadataPolicyOperator> operators;
+
+ /**
+ * Set the list of metadata policy operators used for enforcing the metadata policy.
+ *
+ * @param policyOperators What to set
+ */
+ public void setMetadataPolicyOperators(@Nonnull final List<FederationMetadataPolicyOperator> policyOperators) {
+ checkSetterPreconditions();
+ operators = Constraint.isNotNull(policyOperators, "List of metadata policy operators cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable public Pair<Object, Boolean> apply(@Nullable final Object candidate,
+ @Nullable final MetadataPolicy policy) {
+ if (policy == null) {
+ return new Pair<>(candidate, Boolean.TRUE);
+ }
+ Object operatorResult = candidate;
+ for (final FederationMetadataPolicyOperator operator : operators) {
+ try {
+ operatorResult = operator.apply(operatorResult, policy);
+ } catch (final ConstraintViolationException e) {
+ log.debug("Operator {} returned a non-success result", operator.getClass());
+ return new Pair<>(operatorResult, Boolean.FALSE);
+ }
+ }
+ return new Pair<>(operatorResult, Boolean.TRUE);
+ }
+
+ /**
+ * Checks if the given result of metadata policy operator is a success result.
+ *
+ * @param result operator result to be verified
+ * @return true if success, false otherwise (including null)
+ */
+ protected boolean isSuccessResult(@Nullable final Pair<Object,Boolean> result) {
+ return Optional.ofNullable(result)
+ .map(pair -> pair.getSecond())
+ .map(second -> second.booleanValue())
+ .orElse(false);
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyEssentialOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyEssentialOperator.java
new file mode 100644
index 0000000..bab1b52
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyEssentialOperator.java
@@ -0,0 +1,46 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * Default implementation for the 'essential' -operator.
+ */
+public class DefaultFederationMetadataPolicyEssentialOperator extends AbstractFederationMetadataPolicyOperator {
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getOperatorName() {
+ return "essential";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Object apply(@Nullable final Object candidate, @Nonnull final MetadataPolicy policy)
+ throws ConstraintViolationException {
+ if (candidate != null) {
+ return candidate;
+ }
+ if (policy.isEssential()) {
+ throw new ConstraintViolationException("Essential value is missing");
+ }
+ return candidate;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyMergingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyMergingStrategy.java
new file mode 100644
index 0000000..6f2c80d
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyMergingStrategy.java
@@ -0,0 +1,102 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.MetadataPolicyHelper;
+import net.shibboleth.oidfed.metadata.policy.FederationMetadataPolicyOperator;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A function that merges two maps of metadata policies according to the rules specified in the OID federation spec
+ * (draft 42), section 6.1. The function returns a pair of map of merged metadata policies and a boolean indicating if
+ * the merging operators in the policies were compliant.
+ */
+public class DefaultFederationMetadataPolicyMergingStrategy extends AbstractIdentifiableInitializableComponent
+ implements BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
+ Pair<Map<String, MetadataPolicy>, Boolean>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultFederationMetadataPolicyMergingStrategy.class);
+
+ /** The list of metadata policy operators used for enforcing the metadata policy. */
+ @NonnullAfterInit List<FederationMetadataPolicyOperator> operators;
+
+ /**
+ * Set the list of metadata policy operators used for enforcing the metadata policy.
+ *
+ * @param policyOperators What to set
+ */
+ public void setMetadataPolicyOperators(@Nonnull final List<FederationMetadataPolicyOperator> policyOperators) {
+ checkSetterPreconditions();
+ operators = Constraint.isNotNull(policyOperators, "List of metadata policy operators cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull
+ public Pair<Map<String, MetadataPolicy>, Boolean> apply(@Nullable final Map<String,MetadataPolicy> first,
+ @Nullable final Map<String,MetadataPolicy> second) {
+ if (first == null || first.isEmpty()) {
+ return new Pair<>(second == null ? (first == null ? null : Collections.emptyMap()) : second , Boolean.TRUE);
+ } else if (second == null || second.isEmpty()) {
+ return new Pair<>(first, Boolean.TRUE);
+ }
+ final Set<String> combinedKeys = Stream.concat(first.keySet().stream(),
+ second.keySet().stream()).collect(Collectors.toSet());
+ final Map<String, MetadataPolicy> result = new HashMap<>();
+ boolean valid = true;
+ for (final String key : combinedKeys) {
+ try {
+ final MetadataPolicy merged =
+ MetadataPolicyHelper.mergeMetadataPolicies(first.get(key), second.get(key));
+ log.debug("Merging result of {} with {}: {}", first.get(key), second.get(key), merged);
+ if (merged != null && merged.getOneOfValues() != null && merged.getOneOfValues().isEmpty()) {
+ log.warn("Value of 'one_of' operator is empty after merging");
+ valid = false;
+ }
+ result.put(key, merged);
+ for (final FederationMetadataPolicyOperator operator : operators) {
+ if (merged != null && !operator.validate(merged)) {
+ valid = false;
+ }
+ }
+ } catch (final ConstraintViolationException e) {
+ log.warn("Incompatible metadata policies for claim '{}' that cannot be merged.", key);
+ valid = false;
+ }
+ }
+ return new Pair<>(result, Boolean.valueOf(valid));
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyOneOfOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyOneOfOperator.java
new file mode 100644
index 0000000..114943c
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyOneOfOperator.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.oidfed.metadata.policy.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default implementation for the 'one_of' -operator.
+ */
+public class DefaultFederationMetadataPolicyOneOfOperator extends AbstractFederationMetadataPolicyOperator {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultFederationMetadataPolicyOneOfOperator.class);
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getOperatorName() {
+ return "one_of";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Object apply(@Nullable final Object candidate, @Nonnull final MetadataPolicy policy)
+ throws ConstraintViolationException {
+ final List<Object> oneOf = policy.getOneOfValues();
+ if (candidate == null || oneOf == null) {
+ log.debug("No candidate or one_of value present. nothing to do");
+ return candidate;
+ }
+ if (!oneOf.contains(candidate)) {
+ throw new ConstraintViolationException(
+ "The candidate " + candidate + " is not included in the values for one_of: " + oneOf);
+ }
+ return candidate;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean validate(@Nonnull final MetadataPolicy policy) {
+ final List<Object> oneOf = policy.getOneOfValues();
+ if (oneOf == null) {
+ return true;
+ }
+ final Object value = policy.getValue();
+ if (value != null && !oneOf.contains(value)) {
+ log.debug("The value {} is not included in the values for one_of: {}", value, oneOf);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicySubsetOfOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicySubsetOfOperator.java
new file mode 100644
index 0000000..098b438
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicySubsetOfOperator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.MetadataPolicyHelper;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default implementation for the 'subset_of' -operator.
+ */
+public class DefaultFederationMetadataPolicySubsetOfOperator extends AbstractFederationMetadataPolicyOperator {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultFederationMetadataPolicySubsetOfOperator.class);
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getOperatorName() {
+ return "subset_of";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Object apply(@Nullable final Object candidate, @Nonnull final MetadataPolicy policy)
+ throws ConstraintViolationException {
+ final List<Object> subsetOf = policy.getSubsetOfValues();
+ if (candidate == null || subsetOf == null) {
+ log.debug("No candidate or subset_of value present. nothing to do");
+ return candidate;
+ }
+ if (candidate instanceof List<?> list) {
+ final List<Object> candidateList = list.stream()
+ .filter(Object.class::isInstance)
+ .map(Object.class::cast)
+ .filter(item -> subsetOf.contains(item))
+ .toList();
+ assert candidateList != null;
+ return CollectionSupport.copyToList(candidateList);
+ }
+ log.debug("Candidate value {} is not a list/array", candidate);
+ throw new ConstraintViolationException("Candidate value " + candidate + " is not a list/array");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean validate(@Nonnull final MetadataPolicy policy) {
+ final List<Object> subsetOf = policy.getSubsetOfValues();
+ if (subsetOf == null) {
+ return true;
+ }
+ boolean validation = true;
+ final Object value = policy.getValue();
+ if (value != null && !MetadataPolicyHelper.isSubsetOfValues(value, subsetOf)) {
+ log.debug("The value {} is not a subset of the values for subset_of operator {}", value, subsetOf);
+ validation = false;
+ }
+ final Object add = policy.getAdd();
+ if (add != null && !MetadataPolicyHelper.isSubsetOfValues(add, subsetOf)) {
+ log.debug("The value for add operator {} is not a subset of the values for subset_of operator {}", add,
+ subsetOf);
+ validation = false;
+ }
+ final List<Object> supersetOf = policy.getSupersetOfValues();
+ if (supersetOf != null && !MetadataPolicyHelper.isSupersetOfValues(subsetOf, supersetOf)) {
+ log.debug("The value for subset_of operator {} is not a subset of the values for superset_of operator {}",
+ subsetOf, supersetOf);
+ validation = false;
+ }
+ return validation;
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicySupersetOfOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicySupersetOfOperator.java
new file mode 100644
index 0000000..eec730a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicySupersetOfOperator.java
@@ -0,0 +1,92 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.MetadataPolicyHelper;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default implementation for the 'superset_of' -operator.
+ */
+public class DefaultFederationMetadataPolicySupersetOfOperator extends AbstractFederationMetadataPolicyOperator {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultFederationMetadataPolicySupersetOfOperator.class);
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getOperatorName() {
+ return "superset_of";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Object apply(@Nullable final Object candidate, @Nonnull final MetadataPolicy policy)
+ throws ConstraintViolationException {
+ final List<Object> supersetOf = policy.getSupersetOfValues();
+ if (candidate == null || supersetOf == null) {
+ return candidate;
+ }
+ final List<Object> candidateList;
+ if (candidate instanceof List<?> list) {
+ candidateList = list.stream().filter(Object.class::isInstance).map(Object.class::cast).toList();
+ } else {
+ log.warn("The value for superset_of is not a list/array {}", candidate);
+ throw new ConstraintViolationException("Candidate value " + candidate + " is not a list/array");
+ }
+ assert candidateList != null;
+
+ if (!candidateList.containsAll(supersetOf)) {
+ log.debug("The candidate {} did not contain all the values of superset_of: {}", candidate, supersetOf);
+ throw new ConstraintViolationException(
+ "Candidate value " + candidate + " did not contain all values of superset_of: " + supersetOf);
+ }
+
+ return CollectionSupport.copyToList(candidateList);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean validate(@Nonnull final MetadataPolicy policy) {
+ final List<Object> supersetOf = policy.getSupersetOfValues();
+ if (supersetOf == null) {
+ return true;
+ }
+
+ boolean validation = true;
+ final Object value = policy.getValue();
+ if (value != null && !MetadataPolicyHelper.isSupersetOfValues(value, supersetOf)) {
+ log.debug("The value {} is not a superset of the values for subset_of operator {}", value, supersetOf);
+ validation = false;
+ }
+ final List<Object> subsetOf = policy.getSubsetOfValues();
+ if (subsetOf != null && !MetadataPolicyHelper.isSupersetOfValues(subsetOf, supersetOf)) {
+ log.debug("The value for subset_of operator {} is not a superset of the values for superset_of operator {}",
+ subsetOf, supersetOf);
+ validation = false;
+ }
+ return validation;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyValueOperator.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyValueOperator.java
new file mode 100644
index 0000000..5e82c8c
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/policy/impl/DefaultFederationMetadataPolicyValueOperator.java
@@ -0,0 +1,88 @@
+/*
+ * 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.oidfed.metadata.policy.impl;
+
+import java.util.List;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.MetadataPolicyHelper;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default implementation for the 'value' -operator.
+ */
+public class DefaultFederationMetadataPolicyValueOperator extends AbstractFederationMetadataPolicyOperator {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultFederationMetadataPolicyValueOperator.class);
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getOperatorName() {
+ return "value";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Object apply(@Nullable final Object candidate, @Nonnull final MetadataPolicy policy)
+ throws ConstraintViolationException {
+ final Object value = policy.getValue();
+ return value != null ?value instanceof Optional optional && optional.isEmpty() ? null : value : candidate;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean validate(@Nonnull final MetadataPolicy policy) {
+ final Object value = policy.getValue();
+ if (value instanceof Optional optional && optional.isEmpty()) {
+ if (policy.isEssential()) {
+ log.debug("Value for the value operator is null and essential is true");
+ return false;
+ }
+ if (policy.getDefaultValue() != null) {
+ log.debug("Null value cannot be combined with non-empty default: {}", policy.getDefaultValue());
+ return false;
+ }
+ log.debug("Value for the value operator is null and essential is false");
+ return true;
+ } else if (value == null) {
+ return true;
+ }
+ boolean validation = true;
+ final List<Object> oneOf = policy.getOneOfValues();
+ if (oneOf != null && !oneOf.contains(value)) {
+ log.debug("Value {} is not included in the one_of values {}", value, oneOf);
+ validation = false;
+ }
+ final List<Object> subsetOf = policy.getSubsetOfValues();
+ if (subsetOf != null && !MetadataPolicyHelper.isSubsetOfValues(value, subsetOf)) {
+ log.debug("Value {} is not included in the of subset_of values {}", value, subsetOf);
+ validation = false;
+ }
+ final List<Object> supersetOf = policy.getSupersetOfValues();
+ if (supersetOf != null && !MetadataPolicyHelper.isSupersetOfValues(value, supersetOf)) {
+ log.debug("Value {} is not included in the of superset_of values {}", value, supersetOf);
+ validation = false;
+ }
+ return validation;
+ }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/util/EntityStatementHelper.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/util/EntityStatementHelper.java
new file mode 100644
index 0000000..03d16ed
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/util/EntityStatementHelper.java
@@ -0,0 +1,203 @@
+/*
+ * 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.oidfed.metadata.util;
+
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.impl.EntityConfigurationImpl;
+import net.shibboleth.oidfed.metadata.impl.SubordinateStatementImpl;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Static utility method related to entity statements.
+ */
+public class EntityStatementHelper {
+
+ /** Class logger. */
+ @Nonnull private final static Logger log = LoggerFactory.getLogger(EntityStatementHelper.class);
+
+ /**
+ * Serialize the given trust chain into a comma-separated list of strings.
+ *
+ * @param trustChain input trust chain
+ * @return serialized trust chain
+ */
+ @Nullable
+ public static String getSerializedTrustChain(@Nullable final List<EntityStatement<?>> trustChain) {
+ return trustChain == null ? null :
+ String.join(",", trustChain.stream().map(es -> es.getJwt().serialize()).toList());
+ }
+
+ /**
+ * Serialize the given entity statement into a string.
+ *
+ * @param entityStatement input entity statement
+ * @return serialized entity statement
+ */
+ @Nullable
+ public static String getSerializedEntityStatement(@Nullable final EntityStatement<?> entityStatement) {
+ return entityStatement == null ? null : entityStatement.getJwt().serialize();
+ }
+
+ /**
+ * Get the list of entity IDs of the entities in the given trust chain. The first item is the entity ID of the
+ * leaf entity.
+ *
+ * @param trustChain trust chain
+ * @return list of entity IDs
+ */
+ @Nonnull public static List<String> getEntityIds(@Nonnull final List<EntityStatement<?>> trustChain) {
+ final List<String> result = trustChain.subList(1, trustChain.size()).stream()
+ .filter(Objects::nonNull)
+ .map(statement -> statement.getSubject())
+ .toList();
+ assert result != null;
+ return result;
+ }
+
+ /**
+ * Checks if the given subject already exists in the trust chain.
+ *
+ * @param subject subject to be looked after
+ * @param trustChain trust chain
+ * @return true if subject already exists in the trust chain, false otherwise
+ */
+ public static boolean subjectAlreadyExists(@Nullable final String subject,
+ @Nonnull final List<EntityStatement<?>> trustChain) {
+ return subject == null ? false : trustChain.stream()
+ .filter(Objects::nonNull)
+ .map(statement -> statement.getSubject())
+ .anyMatch(id -> subject.equals(id));
+ }
+
+ /**
+ * Deserialize the given list of strings into a list of entity statements. The method also verifies that the
+ * trust chain syntax is correct: it contains three or more items, it begins and ends with an entity configuration,
+ * other items being subordinate statements.
+ *
+ * @param chain serialized trust chain
+ * @param objectMapper object mapper used for deserializing the statement payloads
+ * @return list of entity statements, or null if the input was invalid
+ */
+ @Nullable public static List<EntityStatement<?>> deserializeTrustChain(
+ @Nonnull @NonnullElements final List<String> chain, @Nonnull final ObjectMapper objectMapper) {
+ if (chain.size() < 3) {
+ log.warn("Invalid trust chain: shorter than three items");
+ return null;
+ }
+ final List<EntityStatement<?>> result = new ArrayList<>();
+ for (final String entry : chain) {
+ try {
+ assert entry != null;
+ result.add(parseEntityStatement(entry, objectMapper));
+ } catch (final ConstraintViolationException e) {
+ log.warn("Could not deserialize the given trust chain", e);
+ return null;
+ }
+ }
+ if (!(result.get(0) instanceof EntityConfiguration)) {
+ log.warn("Trust chain does not begin with an entity configuration");
+ return null;
+ }
+ for (int i = 1; i < result.size() - 1; i++) {
+ if (!(result.get(i) instanceof SubordinateStatement)) {
+ log.warn("Trust chain entry {} is not a subordinate statement", i);
+ return null;
+ }
+ }
+ if (!(result.get(result.size() - 1) instanceof EntityConfiguration)) {
+ log.warn("Trust chain does not end with an entity configuration");
+ return null;
+ }
+ return result;
+ }
+
+ /**
+ * Parse an entity statement from the given input. If the subject and issuer matches in the payload, an entity
+ * configuration is returned. Otherwise, a subordinate statement is returned.
+ *
+ * @param input serialized entity statement
+ * @param objectMapper object mapper used for deserializing the statement payload
+ * @return entity configuration or subordinate statement, edpending on the content
+ * @throws ConstraintViolationException if input was invalid
+ */
+ @Nonnull public static EntityStatement<?> parseEntityStatement(@Nonnull final String input,
+ @Nonnull final ObjectMapper objectMapper) throws ConstraintViolationException {
+ final SignedJWT jwt;
+ final JWTClaimsSet claimsSet;
+ try {
+ jwt = SignedJWT.parse(input);
+ assert jwt != null;
+ claimsSet = jwt.getJWTClaimsSet();
+ } catch (final ParseException e) {
+ log.warn("Could not construct a JWT from the entity statement", e);
+ throw new ConstraintViolationException(e.getMessage());
+ }
+ assert objectMapper != null;
+ final String subject = claimsSet.getSubject();
+ if (subject == null) {
+ throw new ConstraintViolationException("Subject is null");
+ }
+ final String issuer = claimsSet.getIssuer();
+ if (issuer == null) {
+ throw new ConstraintViolationException("Issuer is null");
+ }
+ try {
+ if (subject.equals(issuer)) {
+ return EntityConfigurationImpl.parse(jwt, objectMapper);
+ } else {
+ return SubordinateStatementImpl.parse(jwt, objectMapper);
+ }
+ } catch (final JsonProcessingException e) {
+ log.debug("Could not parse the entity statement " + subject + ", issued by " + issuer, e);
+ throw new ConstraintViolationException(e.getMessage());
+ }
+ }
+
+ /**
+ * Parse the expiration time from the trust chain: the lowest expiration time value is taken from the chain.
+ *
+ * @param trustChain trust chain
+ * @return expiration instant
+ */
+ @Nonnull public static Instant parseTrustChainExpiration(@Nonnull final List<EntityStatement<?>> trustChain) {
+ final Instant result = trustChain.stream()
+ .map(statement -> statement.getParsedPayload().getExpiration())
+ .min(Comparator.comparing(Instant::toEpochMilli))
+ .orElse(Instant.now());
+ assert result != null;
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/package-info.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/package-info.java
new file mode 100644
index 0000000..b230ccd
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Top level OIDFed common plugin classes.
+ */
+package net.shibboleth.oidfed;
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/AbstractOIDFederationRegistrationProfileConfiguration.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/AbstractOIDFederationRegistrationProfileConfiguration.java
new file mode 100644
index 0000000..4d758dd
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/AbstractOIDFederationRegistrationProfileConfiguration.java
@@ -0,0 +1,196 @@
+/*
+ * 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.oidfed.profile.config.impl;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidfed.profile.config.OIDFederationRegistrationProfileConfiguration;
+import net.shibboleth.profile.config.AbstractConditionalProfileConfiguration;
+import net.shibboleth.shared.annotation.ParameterName;
+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;
+
+/**
+ * Abstract implementation class for profile configurations related OpenID Federation client registration.
+ */
+public class AbstractOIDFederationRegistrationProfileConfiguration extends AbstractConditionalProfileConfiguration
+ implements OIDFederationRegistrationProfileConfiguration {
+
+ /** Lookup function to local metadata policy to be merged into the federation policy. */
+ @Nonnull private Function<ProfileRequestContext,Map<String, MetadataPolicy>> localMetadataPolicyLookupStrategy;
+
+ /** 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.
+ *
+ * @param id ID of the communication profile, never null or empty
+ */
+ public AbstractOIDFederationRegistrationProfileConfiguration(
+ @Nonnull @NotEmpty @ParameterName(name="id") final String id) {
+ super(id);
+ localMetadataPolicyLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyMap());
+ mandatoryTrustMarksLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ maximumTrustMarkLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofDays(365));
+ remoteTrustMarkValidationCondition = PredicateSupport.alwaysTrue();
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull @NonnullElements @NotLive @Unmodifiable
+ public Map<String, MetadataPolicy> getLocalMetadataPolicy(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ final Map<String, MetadataPolicy> policy = localMetadataPolicyLookupStrategy.apply(profileRequestContext);
+ if (policy != null) {
+ return CollectionSupport.copyToMap(policy);
+ }
+ return CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Set local metadata policy to be merged into the federation policy.
+ *
+ * @param policy metadata policy
+ */
+ public void setLocalMetadataPolicy(
+ @Nonnull @NonnullElements @NotLive @Unmodifiable final Map<String, MetadataPolicy> policy) {
+ localMetadataPolicyLookupStrategy = FunctionSupport.constant(policy);
+ }
+
+ /**
+ * Sets lookup strategy for local metadata policy to be merged into the federation policy.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setLocalMetadataPolicyLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,Map<String, MetadataPolicy>> strategy) {
+ localMetadataPolicyLookupStrategy =
+ Constraint.isNotNull(strategy, "Local metadata policy 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");
+ }
+
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/AbstractOIDFederationResponseCachingProfileConfiguration.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/AbstractOIDFederationResponseCachingProfileConfiguration.java
new file mode 100644
index 0000000..d0f5ba3
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/AbstractOIDFederationResponseCachingProfileConfiguration.java
@@ -0,0 +1,90 @@
+/*
+ * 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.oidfed.profile.config.impl;
+
+import java.time.Duration;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationResponseCachingProfileConfiguration;
+import net.shibboleth.profile.config.AbstractConditionalProfileConfiguration;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+/**
+ * Abstract implementation class for profile configurations related OpenID Federation response caching configuration.
+ */
+public abstract class AbstractOIDFederationResponseCachingProfileConfiguration
+ extends AbstractConditionalProfileConfiguration
+ implements OIDFederationResponseCachingProfileConfiguration {
+
+ /** Lookup function to supply cached success response lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> cachedSuccessResponseLifetimeLookupStrategy;
+
+ /**
+ * Constructor.
+ *
+ * @param id ID of the communication profile, never null or empty
+ */
+ public AbstractOIDFederationResponseCachingProfileConfiguration(
+ @Nonnull @NotEmpty @ParameterName(name="id") final String id) {
+ super(id);
+ cachedSuccessResponseLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofMinutes(5));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getCachedSuccessResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = cachedSuccessResponseLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Success response lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of a cached success response.
+ *
+ * @param lifetime lifetime of a cached success response
+ */
+ public void setCachedSuccessResponseLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration successLifetime = Constraint.isNotNull(lifetime,
+ "Cached success response lifetime cannot be null");
+ Constraint.isTrue(!successLifetime.isZero() && !successLifetime.isNegative(),
+ "Cached success response lifetime must be greater than 0");
+
+ cachedSuccessResponseLifetimeLookupStrategy = FunctionSupport.constant(successLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the cached success response lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setCachedSuccessResponseLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ cachedSuccessResponseLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationAutomaticRegistrationProfileConfiguration.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationAutomaticRegistrationProfileConfiguration.java
new file mode 100644
index 0000000..a19d101
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationAutomaticRegistrationProfileConfiguration.java
@@ -0,0 +1,117 @@
+/*
+ * 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.oidfed.profile.config.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationAutomaticRegistrationProfileConfiguration;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Implementation of a profile configuration for the OpenID Federation Automatic Registration.
+ */
+public class DefaultOIDFederationAutomaticRegistrationProfileConfiguration
+ extends AbstractOIDFederationRegistrationProfileConfiguration
+ implements OIDFederationAutomaticRegistrationProfileConfiguration {
+
+ /** OIDC provider information profile counter name. */
+ @Nonnull @NotEmpty
+ public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.automaticregistration";
+
+ /** Lookup function to supply claims set decoration type. */
+ @Nonnull private Function<ProfileRequestContext,String> claimsSetDecorationTypeLookupStrategy;
+
+ /** Lookup function to supply claims set decoration type for clients with request-provided trust chains. */
+ @Nonnull private Function<ProfileRequestContext,String> providedChainClaimsSetDecorationTypeLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public DefaultOIDFederationAutomaticRegistrationProfileConfiguration() {
+ this(PROFILE_ID);
+ }
+
+ /**
+ * Creates a new configuration instance.
+ *
+ * @param profileId Unique profile identifier.
+ */
+ public DefaultOIDFederationAutomaticRegistrationProfileConfiguration(@Nonnull @NotEmpty final String profileId) {
+ super(profileId);
+ claimsSetDecorationTypeLookupStrategy = FunctionSupport.constant(null);
+ providedChainClaimsSetDecorationTypeLookupStrategy = FunctionSupport.constant(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable @NotEmpty
+ public String getDefaultClaimsSetDecorationType(@Nullable final ProfileRequestContext profileRequestContext) {
+ return StringSupport.trimOrNull(claimsSetDecorationTypeLookupStrategy.apply(profileRequestContext));
+ }
+
+ /**
+ * Set claims set decoration type.
+ *
+ * @param type decoration type, or null for default
+ */
+ public void setDefaultClaimsSetDecorationType(@Nullable @NotEmpty final String type) {
+ claimsSetDecorationTypeLookupStrategy = FunctionSupport.constant(StringSupport.trimOrNull(type));
+ }
+
+ /**
+ * Set lookup strategy for claims set decoration type.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setDefaultClaimsSetDecorationTypeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,String> strategy) {
+ claimsSetDecorationTypeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable @NotEmpty
+ public String getProvidedChainClaimsSetDecorationType(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ return StringSupport.trimOrNull(
+ providedChainClaimsSetDecorationTypeLookupStrategy.apply(profileRequestContext));
+ }
+
+ /**
+ * Set claims set decoration type for clients with request-provided trust chain.
+ *
+ * @param type decoration type
+ */
+ public void setProvidedChainClaimsSetDecorationType(@Nullable @NotEmpty final String type) {
+ providedChainClaimsSetDecorationTypeLookupStrategy = FunctionSupport.constant(StringSupport.trimOrNull(type));
+ }
+
+ /**
+ * Set lookup strategy for claims set decoration type for clients with request-provided trust chain.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProvidedChainClaimsSetDecorationTypeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,String> strategy) {
+ providedChainClaimsSetDecorationTypeLookupStrategy =
+ Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationEntityConfigurationProfileConfiguration.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationEntityConfigurationProfileConfiguration.java
new file mode 100644
index 0000000..230ad84
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationEntityConfigurationProfileConfiguration.java
@@ -0,0 +1,237 @@
+/*
+ * 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.oidfed.profile.config.impl;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationEntityConfigurationProfileConfiguration;
+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;
+
+/**
+ * Implementation of a profile configuration for the OpenID Federation Entity Configuration.
+ */
+public class DefaultOIDFederationEntityConfigurationProfileConfiguration
+ extends AbstractOIDFederationResponseCachingProfileConfiguration
+ implements OIDFederationEntityConfigurationProfileConfiguration {
+
+ /** OIDC provider information profile counter name. */
+ @Nonnull @NotEmpty public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.configuration";
+
+ /** Lookup function to override issuer value. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Lookup function to authority hints. */
+ @Nonnull private Function<ProfileRequestContext,List<String>> authorityHintsLookupStrategy;
+
+ /** Lookup function to supply entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
+
+ /** Lookup function to supply strategy bi-function for manipulating entity statement claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ entityStatementClaimsSetManipulationStrategyLookupStrategy;
+
+ /** Lookup function to supply map of strategies for optional claims to be included in the entity configuration. */
+ @Nonnull
+ private Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>>
+ optionalClaimsLookupStrategiesLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public DefaultOIDFederationEntityConfigurationProfileConfiguration() {
+ this(PROFILE_ID);
+ }
+
+ /**
+ * Creates a new configuration instance.
+ *
+ * @param profileId Unique profile identifier.
+ */
+ public DefaultOIDFederationEntityConfigurationProfileConfiguration(@Nonnull @NotEmpty final String profileId) {
+ super(profileId);
+ issuerLookupStrategy = FunctionSupport.constant(null);
+ authorityHintsLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofHours(24));
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(null);
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable @NotEmpty public String getIssuer(@Nullable final ProfileRequestContext profileRequestContext) {
+ return issuerLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set overridden issuer value.
+ *
+ * @param issuer issuer value
+ */
+ public void setIssuer(@Nullable @NotEmpty final String issuer) {
+ issuerLookupStrategy = FunctionSupport.constant(issuer);
+ }
+
+ /**
+ * Sets lookup strategy for overridden issuer value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull @NonnullElements @NotLive @Unmodifiable
+ public List<String> getAuthorityHints(@Nullable final ProfileRequestContext profileRequestContext) {
+ final List<String> authorityHints = authorityHintsLookupStrategy.apply(profileRequestContext);
+ if (authorityHints != null) {
+ return CollectionSupport.copyToList(authorityHints);
+ }
+ return CollectionSupport.emptyList();
+ }
+
+ /**
+ * Set authority hints value.
+ *
+ * @param hints authority hints
+ */
+ public void setAuthorityHints(@Nonnull @NonnullElements @NotLive @Unmodifiable final List<String> hints) {
+ authorityHintsLookupStrategy = FunctionSupport.constant(hints);
+ }
+
+ /**
+ * Sets lookup strategy for authority hints value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthorityHintsLookupStrategy(@Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ authorityHintsLookupStrategy = Constraint.isNotNull(strategy, "Authority hints lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = entityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of an entity statement.
+ *
+ * @param lifetime lifetime of an entity statement
+ */
+ public void setEntityStatementLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration statementLifetime = Constraint.isNotNull(lifetime, "Entity statement lifetime cannot be null");
+ Constraint.isTrue(!statementLifetime.isZero() && !statementLifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(statementLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ entityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>
+ getEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ return entityStatementClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy bi-function for manipulating entity statement claims set
+ */
+ public void setEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(strategy);
+ }
+
+ /**
+ * Set a lookup strategy for the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Map<String,Function<ProfileRequestContext,Object>> getOptionalClaimsLookupStrategies(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ final Map<String,Function<ProfileRequestContext,Object>> strategies =
+ optionalClaimsLookupStrategiesLookupStrategy.apply(profileRequestContext);
+ return strategies != null ? strategies : CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Set the lookup strategies for optional claims to be included to the entity configuration.
+ *
+ * @param strategies lookup strategies for optional claims to be included to the entity configuration
+ */
+ public void setOptionalClaimsLookupStrategies(
+ @Nullable final Map<String,Function<ProfileRequestContext,Object>> strategies) {
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(strategies);
+ }
+
+ /**
+ * Set a lookup strategy for the lookup strategies for optional claims to be included to the entity configuration.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setOptionalClaimsLookupStrategiesLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>> strategy) {
+ optionalClaimsLookupStrategiesLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationExplicitRegistrationProfileConfiguration.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationExplicitRegistrationProfileConfiguration.java
new file mode 100644
index 0000000..961e65f
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationExplicitRegistrationProfileConfiguration.java
@@ -0,0 +1,341 @@
+/*
+ * 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.oidfed.profile.config.impl;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationExplicitRegistrationProfileConfiguration;
+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;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Implementation of a profile configuration for the OpenID Federation Explicit Registration.
+ */
+public class DefaultOIDFederationExplicitRegistrationProfileConfiguration
+ extends AbstractOIDFederationRegistrationProfileConfiguration
+ implements OIDFederationExplicitRegistrationProfileConfiguration {
+
+ /** OIDC provider information profile counter name. */
+ @Nonnull @NotEmpty
+ public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.explicitregistration";
+
+ /** Lookup function to override issuer value. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Predicate used to indicate whether authorization code flow is supported by this profile. Default true. */
+ @Nonnull private Predicate<ProfileRequestContext> authorizationCodeFlowPredicate;
+
+ /** Predicate used to indicate whether implicit flow is supported by this profile. Default true. */
+ @Nonnull private Predicate<ProfileRequestContext> implicitFlowPredicate;
+
+ /** Predicate used to indicate whether refresh tokens are supported by this profile. Default true. */
+ @Nonnull private Predicate<ProfileRequestContext> refreshTokensPredicate;
+
+ /** Enabled token endpoint authentication methods. */
+ @Nonnull private Function<ProfileRequestContext,Set<String>> tokenEndpointAuthMethodsLookupStrategy;
+
+ /** Lookup function to supply entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
+
+ /** Lookup function to supply strategy bi-function for manipulating entity statement claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ entityStatementClaimsSetManipulationStrategyLookupStrategy;
+
+ /** Lookup function to supply map of strategies for optional claims to be included in the entity statement. */
+ @Nonnull
+ private Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>>
+ optionalClaimsLookupStrategiesLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public DefaultOIDFederationExplicitRegistrationProfileConfiguration() {
+ this(PROFILE_ID);
+ }
+
+ /**
+ * Creates a new configuration instance.
+ *
+ * @param profileId Unique profile identifier.
+ */
+ public DefaultOIDFederationExplicitRegistrationProfileConfiguration(@Nonnull @NotEmpty final String profileId) {
+ super(profileId);
+ issuerLookupStrategy = FunctionSupport.constant(null);
+ authorizationCodeFlowPredicate = PredicateSupport.alwaysTrue();
+ implicitFlowPredicate = PredicateSupport.alwaysTrue();
+ refreshTokensPredicate = PredicateSupport.alwaysTrue();
+ tokenEndpointAuthMethodsLookupStrategy = FunctionSupport.constant(
+ CollectionSupport.setOf(
+ ClientAuthenticationMethod.CLIENT_SECRET_BASIC.toString(),
+ ClientAuthenticationMethod.CLIENT_SECRET_POST.toString(),
+ ClientAuthenticationMethod.CLIENT_SECRET_JWT.toString(),
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT.toString()));
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(null);
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofHours(24));
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable @NotEmpty public String getIssuer(@Nullable final ProfileRequestContext profileRequestContext) {
+ return issuerLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set overridden issuer value.
+ *
+ * @param issuer issuer value
+ */
+ public void setIssuer(@Nullable @NotEmpty final String issuer) {
+ issuerLookupStrategy = FunctionSupport.constant(issuer);
+ }
+
+ /**
+ * Sets lookup strategy for overridden issuer value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isAuthorizationCodeFlowEnabled(@Nullable final ProfileRequestContext profileRequestContext) {
+ return authorizationCodeFlowPredicate.test(profileRequestContext);
+ }
+
+ /**
+ * Set whether authorization code flow is supported by this profile.
+ *
+ * @param flag flag to set
+ */
+ public void setAuthorizationCodeFlowEnabled(final boolean flag) {
+ authorizationCodeFlowPredicate = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+ }
+
+ /**
+ * Set condition used to indicate whether authorization code flow is supported by this profile.
+ *
+ * @param condition condition to set
+ */
+ public void setAuthorizationCodeFlowEnabledPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ authorizationCodeFlowPredicate = Constraint.isNotNull(condition,
+ "Condition used to indicate whether authorization code flow is supported cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isImplicitFlowEnabled(@Nullable final ProfileRequestContext profileRequestContext) {
+ return implicitFlowPredicate.test(profileRequestContext);
+ }
+
+ /**
+ * Set whether hybrid flow is supported by this profile.
+ *
+ * @param flag flag to set
+ */
+ public void setImplicitFlowEnabled(final boolean flag) {
+ implicitFlowPredicate = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+ }
+
+ /**
+ * Set condition used to indicate whether hybrid flow is supported by this profile.
+ *
+ * @param condition condition to set.
+ */
+ public void setImplicitFlowEnabledPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ implicitFlowPredicate = Constraint.isNotNull(condition,
+ "Condition used to indicate whether hybrid flow is supported cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isRefreshTokensEnabled(@Nullable final ProfileRequestContext profileRequestContext) {
+ return refreshTokensPredicate.test(profileRequestContext);
+ }
+
+ /**
+ * Set whether refresh tokens are supported by this profile.
+ *
+ * @param flag flag to set
+ */
+ public void setRefreshTokensEnabled(final boolean flag) {
+ refreshTokensPredicate = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+ }
+
+ /**
+ * Set condition used to indicate whether refresh tokens are supported by this profile.
+ *
+ * @param condition condition to set
+ */
+ public void setRefreshTokensEnabledPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ refreshTokensPredicate = Constraint.isNotNull(condition,
+ "Condition used to indicate whether refresh tokens are supported cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull @NonnullElements @NotLive @Unmodifiable public Set<String> getTokenEndpointAuthMethods(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+
+ final Collection<String> methods = tokenEndpointAuthMethodsLookupStrategy.apply(profileRequestContext);
+ if (methods != null) {
+ return CollectionSupport.copyToSet(methods);
+ }
+ return CollectionSupport.emptySet();
+ }
+
+ /**
+ * Set the enabled token endpoint authentication methods.
+ *
+ * @param methods What to set.
+ */
+ public void setTokenEndpointAuthMethods(@Nonnull @NonnullElements final Collection<String> methods) {
+ Constraint.isNotNull(methods, "Collection of methods cannot be null");
+
+ tokenEndpointAuthMethodsLookupStrategy =
+ FunctionSupport.constant(Set.copyOf(StringSupport.normalizeStringCollection(methods)));
+ }
+
+ /**
+ * Set a lookup strategy for the enabled token endpoint authentication methods.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setTokenEndpointAuthMethodsLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,Set<String>> strategy) {
+ tokenEndpointAuthMethodsLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>
+ getEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ return entityStatementClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy bi-function for manipulating entity statement claims set
+ */
+ public void setEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(strategy);
+ }
+
+ /**
+ * Set a lookup strategy for the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = entityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of an entity statement.
+ *
+ * @param lifetime lifetime of an entity statement
+ */
+ public void setEntityStatementLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration statementLifetime = Constraint.isNotNull(lifetime, "Entity statement lifetime cannot be null");
+ Constraint.isTrue(!statementLifetime.isZero() && !statementLifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(statementLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ entityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Map<String,Function<ProfileRequestContext,Object>> getOptionalClaimsLookupStrategies(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ final Map<String,Function<ProfileRequestContext,Object>> strategies =
+ optionalClaimsLookupStrategiesLookupStrategy.apply(profileRequestContext);
+ return strategies != null ? strategies : CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Set the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param strategies lookup strategies for optional claims to be included to the entity statement
+ */
+ public void setOptionalClaimsLookupStrategies(
+ @Nullable final Map<String,Function<ProfileRequestContext,Object>> strategies) {
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(strategies);
+ }
+
+ /**
+ * Set a lookup strategy for the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setOptionalClaimsLookupStrategiesLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>> strategy) {
+ optionalClaimsLookupStrategiesLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationResolveEntityProfileConfiguration.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationResolveEntityProfileConfiguration.java
new file mode 100644
index 0000000..e45f1b6
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationResolveEntityProfileConfiguration.java
@@ -0,0 +1,274 @@
+/*
+ * 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.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;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.profile.oauth2.config.impl.AbstractOAuth2ClientAuthenticableProfileConfiguration;
+import net.shibboleth.oidfed.profile.config.OIDFederationResolveEntityProfileConfiguration;
+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.
+ */
+public class DefaultOIDFederationResolveEntityProfileConfiguration
+ extends AbstractOAuth2ClientAuthenticableProfileConfiguration
+ implements OIDFederationResolveEntityProfileConfiguration {
+
+ /** OIDC provider information profile counter name. */
+ @Nonnull @NotEmpty public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.resolve-entity";
+
+ /** Lookup function to override issuer value. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Lookup function to supply cached success response lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> cachedSuccessResponseLifetimeLookupStrategy;
+
+ /** 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.
+ */
+ public DefaultOIDFederationResolveEntityProfileConfiguration() {
+ this(PROFILE_ID);
+ }
+
+ /**
+ * Creates a new configuration instance.
+ *
+ * @param profileId Unique profile identifier.
+ */
+ public DefaultOIDFederationResolveEntityProfileConfiguration(@Nonnull @NotEmpty final String profileId) {
+ super(profileId);
+ 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} */
+ @Override
+ @Nullable @NotEmpty public String getIssuer(@Nullable final ProfileRequestContext profileRequestContext) {
+ return issuerLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set overridden issuer value.
+ *
+ * @param issuer issuer value
+ */
+ public void setIssuer(@Nullable @NotEmpty final String issuer) {
+ issuerLookupStrategy = FunctionSupport.constant(issuer);
+ }
+
+ /**
+ * Sets lookup strategy for overridden issuer value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getCachedSuccessResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = cachedSuccessResponseLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Success response lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of a cached success response.
+ *
+ * @param lifetime lifetime of a cached success response
+ */
+ public void setCachedSuccessResponseLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration successLifetime = Constraint.isNotNull(lifetime,
+ "Cached success response lifetime cannot be null");
+ Constraint.isTrue(!successLifetime.isZero() && !successLifetime.isNegative(),
+ "Cached success response lifetime must be greater than 0");
+
+ cachedSuccessResponseLifetimeLookupStrategy = FunctionSupport.constant(successLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the cached success response lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setCachedSuccessResponseLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ cachedSuccessResponseLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getCachedErrorResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = cachedErrorResponseLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Error response lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of a cached error response.
+ *
+ * @param lifetime lifetime of a cached error response
+ */
+ public void setCachedErrorResponseLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration errorLifetime = Constraint.isNotNull(lifetime,
+ "Cached error response lifetime cannot be null");
+ Constraint.isTrue(!errorLifetime.isZero() && !errorLifetime.isNegative(),
+ "Cached error response lifetime must be greater than 0");
+
+ cachedErrorResponseLifetimeLookupStrategy = FunctionSupport.constant(errorLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the cached error response lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setCachedErrorResponseLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ 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/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationSignedKeysetProfileConfiguration.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationSignedKeysetProfileConfiguration.java
new file mode 100644
index 0000000..16fc150
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/config/impl/DefaultOIDFederationSignedKeysetProfileConfiguration.java
@@ -0,0 +1,199 @@
+/*
+ * 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.oidfed.profile.config.impl;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidfed.profile.config.OIDFederationSignedKeysetProfileConfiguration;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+/**
+ * Implementation of a profile configuration for the OpenID Federation Signed keyset.
+ */
+public class DefaultOIDFederationSignedKeysetProfileConfiguration
+ extends AbstractOIDFederationResponseCachingProfileConfiguration
+ implements OIDFederationSignedKeysetProfileConfiguration {
+
+ /** OIDC provider information profile counter name. */
+ @Nonnull @NotEmpty public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.keyset";
+
+ /** Lookup function to override issuer value. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Lookup function to supply entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
+
+ /** Lookup function to supply strategy bi-function for manipulating entity statement claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ entityStatementClaimsSetManipulationStrategyLookupStrategy;
+
+ /** Lookup function to supply map of strategies for optional claims to be included in the entity statement. */
+ @Nonnull
+ private Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>>
+ optionalClaimsLookupStrategiesLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public DefaultOIDFederationSignedKeysetProfileConfiguration() {
+ this(PROFILE_ID);
+ }
+
+ /**
+ * Creates a new configuration instance.
+ *
+ * @param profileId Unique profile identifier.
+ */
+ public DefaultOIDFederationSignedKeysetProfileConfiguration(@Nonnull @NotEmpty final String profileId) {
+ super(profileId);
+ issuerLookupStrategy = FunctionSupport.constant(null);
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(null);
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofHours(24));
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable @NotEmpty public String getIssuer(@Nullable final ProfileRequestContext profileRequestContext) {
+ return issuerLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set overridden issuer value.
+ *
+ * @param issuer issuer value
+ */
+ public void setIssuer(@Nullable @NotEmpty final String issuer) {
+ issuerLookupStrategy = FunctionSupport.constant(issuer);
+ }
+
+ /**
+ * Sets lookup strategy for overridden issuer value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = entityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isNegative(),
+ "Entity statement lifetime must be equal to or greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of an entity statement.
+ *
+ * @param lifetime lifetime of an entity statement
+ */
+ public void setEntityStatementLifetime(@Nonnull final Duration lifetime) {
+ final Duration statementLifetime = Constraint.isNotNull(lifetime, "Entity statement lifetime cannot be null");
+ Constraint.isTrue(!statementLifetime.isNegative(),
+ "Entity statement lifetime must be equal or greater than 0");
+
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(statementLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ entityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>
+ getEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ return entityStatementClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy bi-function for manipulating entity statement claims set
+ */
+ public void setEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(strategy);
+ }
+
+ /**
+ * Set a lookup strategy for the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Map<String,Function<ProfileRequestContext,Object>> getOptionalClaimsLookupStrategies(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ final Map<String,Function<ProfileRequestContext,Object>> strategies =
+ optionalClaimsLookupStrategiesLookupStrategy.apply(profileRequestContext);
+ return strategies != null ? strategies : CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Set the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param strategies lookup strategies for optional claims to be included to the entity statement
+ */
+ public void setOptionalClaimsLookupStrategies(
+ @Nullable final Map<String,Function<ProfileRequestContext,Object>> strategies) {
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(strategies);
+ }
+
+ /**
+ * Set a lookup strategy for the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setOptionalClaimsLookupStrategiesLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>> strategy) {
+ optionalClaimsLookupStrategiesLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/oidfed-common-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
new file mode 100644
index 0000000..b43cf42
--- /dev/null
+++ b/oidfed-common-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -0,0 +1,1404 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize" default-destroy-method="destroy">
+
+ <bean id="shibboleth.oidfed.DefaultOpenIdConfigurationResolver"
+ class="net.shibboleth.idp.plugin.oidc.op.metadata.impl.DynamicFilesystemProviderMetadataResolver"
+ p:minRefreshDelay="%{idp.oidc.config.minRefreshDelay:PT5M}"
+ p:maxRefreshDelay="%{idp.oidc.config.maxRefreshDelay:PT4H}"
+ c:metadata="#{getObject('shibboleth.oidfed.OpenIDConfiguration') ?: getObject('DefaultMetadataSkeleton')}"
+ p:dynamicValueResolvers-ref="#{'%{idp.oidfed.configuration.resolver.values:shibboleth.oidfed.configuration.DefaultDynamicValueResolvers}'.trim()}"/>
+
+ <bean id="shibboleth.oidfed.configuration.DefaultDynamicValueResolvers"
+ parent="shibboleth.oidc.discovery.DefaultDynamicValueResolvers"
+ class="org.springframework.beans.factory.config.MapFactoryBean">
+ <property name="sourceMap">
+ <map merge="true">
+ <entry key="federation_registration_endpoint" value-ref="shibboleth.oidfed.configuration.FederationRegistrationEndpointResolver" />
+ <entry key="client_registration_types_supported" value-ref="shibboleth.oidfed.configuration.RegistrationTypesResolver" />
+ </map>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.configuration.FederationRegistrationEndpointResolver"
+ class="net.shibboleth.idp.plugin.oidc.op.metadata.impl.FunctionMetadataValueResolver">
+ <property name="resolverFunction">
+ <bean parent="shibboleth.Functions.Expression"
+ c:expression="#custom.get().getRequestURL().toString().replace('/profile/oidfed/entity-configuration','/profile/oidfed/register').replace('/.well-known/openid-federation','/profile/oidfed/register')"
+ p:customObject-ref="shibboleth.HttpServletRequestSupplier" />
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.configuration.RegistrationTypesResolver"
+ class="net.shibboleth.idp.plugin.oidc.op.metadata.impl.FunctionMetadataValueResolver">
+ <property name="resolverFunction">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.util.List" factory-method="copyOf">
+ <constructor-arg value="%{idp.oidfed.entityConfiguration.registrationTypesSupported:automatic,explicit}" />
+ </bean>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.EntityConfigurationMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultEntityConfigurationMetadataCache" parent="shibboleth.oidfed.EntityConfigurationMetadataCacheBuilderSpec"
+ p:cleanupTaskInterval="%{idp.oidfed.cache.entityConfiguration.cleanupTaskInterval:PT5M}"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.SubordinateEntityStatementMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultSubordinateEntityStarementMetadataCache" parent="shibboleth.oidfed.SubordinateEntityStatementMetadataCacheBuilderSpec"
+ p:cleanupTaskInterval="%{idp.oidfed.cache.subordinateStatement.cleanupTaskInterval:PT5M}"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.TrustChainMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultTrustChainMetadataCache" parent="shibboleth.oidfed.TrustChainMetadataCacheBuilderSpec"
+ p:cleanupTaskInterval="%{idp.oidfed.cache.trustChain.cleanupTaskInterval:PT5M}"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.ResolveEntityTrustChainMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultResolveEntityTrustChainMetadataCache" parent="shibboleth.oidfed.ResolveEntityTrustChainMetadataCacheBuilderSpec"
+ p:cleanupTaskInterval="%{idp.oidfed.cache.resolveEntity.cleanupTaskInterval:PT5M}"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.LocalTrustAnchorsMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultLocalTrustAnchorsMetadataCache" parent="shibboleth.oidfed.LocalTrustAnchorsMetadataCacheBuilderSpec"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.SignedKeysetMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultSignedKeysetMetadataCache" parent="shibboleth.oidfed.SignedKeysetMetadataCacheBuilderSpec"
+ p:cleanupTaskInterval="%{idp.oidfed.cache.signedKeyset.cleanupTaskInterval:PT5M}"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="DefaultResponseContainerExpirationTimeStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultResponseContainerExpirationTimeStrategy"/>
+
+ <bean id="shibboleth.oidfed.EntityConfigurationMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+ p:criteriaToIdentifierStrategy-ref="shibboleth.oidfed.DefaultSubjectEntityIDCriteriaToIdentifierStrategy"
+ p:minCacheDuration="%{idp.oidfed.cache.entityConfiguration.minRefreshDelay:PT1M}"
+ p:maxCacheDuration="%{idp.oidfed.cache.entityConfiguration.maxRefreshDelay:PT30M}"
+ p:metadataExpirationTimeStrategy-ref="DefaultResponseContainerExpirationTimeStrategy">
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getEntityId()"/>
+ </property>
+ <property name="fetchStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.configuration.DefaultEntityConfigurationFetchingStrategy"
+ p:httpClient="#{getObject('shibboleth.oidfed.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+ p:httpClientSecurityParameters="#{getObject('shibboleth.oidfed.NonBrowser.HttpClientSecurityParameters')}"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper">
+ <property name="criteriaToValidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.entityConfiguration.validContainerLifetime:PT5M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToInvalidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.entityConfiguration.invalidContainerLifetime:PT1M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToExceptionContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.entityConfiguration.exceptionContainerLifetime:PT0S}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementMetadataFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.entityConfiguration.customFilterStrategies:}'.trim())}">
+ <property name="validationConditions">
+ <util:list value-type="java.util.function.BiPredicate">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementSignatureValidationCondition"
+ p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine">
+ <property name="criteriaSetLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.configuration.DefaultEntityConfigurationCriteriaSetLookupFunction"/>
+ </property>
+ </bean>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.entityConfiguration.claimsValidator:shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
+ </bean>
+ </util:list>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.oidfed.cache.entityConfiguration.maxJwtLifetime:PT24H}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="jwks" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="metadata_policy" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="metadata_policy_crit" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="constraints" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="source_endpoint" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="aud" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.CritClaimsValidator"
+ p:recognizedClaims="%{idp.oidfed.cache.entityConfiguration.critClaims:%{idp.oidfed.cache.default.critClaims:}}" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.TrustMarksClaimsValidator" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.NonEmptyStringArrayClaimsValidator"
+ p:nonEmptyArrayClaims="authority_hints,trust_anchor_hints" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.TrustMarkOwnersClaimsValidator" />
+ </util:list>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"
+ class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultEntityConfigurationCredentialResolver" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultSubordinateStatementTrustEngine"
+ class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultSubordinateStatementCredentialResolver" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultSubjectEntityIDCriteriaToIdentifierStrategy"
+ parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityIDCriterion))?.getValue()"/>
+
+ <bean id="shibboleth.oidfed.SubordinateEntityStatementMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+ p:minCacheDuration="%{idp.oidfed.cache.subordinateStatement.minRefreshDelay:PT1M}"
+ p:maxCacheDuration="%{idp.oidfed.cache.subordinateStatement.maxRefreshDelay:PT30M}"
+ p:metadataExpirationTimeStrategy-ref="DefaultResponseContainerExpirationTimeStrategy">
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getIdentifier()"/>
+ </property>
+ <property name="criteriaToIdentifierStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.subordinate.DefaultSubordinateStatementCriteriaToIdentifierStrategy"
+ p:criteriaToSubjectEntityIdStrategy-ref="shibboleth.oidfed.DefaultSubjectEntityIDCriteriaToIdentifierStrategy">
+ <property name="criteriaToIssuerEntityIdStrategy">
+ <bean parent="shibboleth.Functions.Expression"
+ c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.IssuerEntityIDCriterion))?.getValue()"/>
+ </property>
+ </bean>
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementMetadataFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.subordinateStatement.customFilterStrategies:}'.trim())}">
+ <property name="validationConditions">
+ <util:list value-type="java.util.function.BiPredicate">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementSignatureValidationCondition"
+ p:trustEngine-ref="shibboleth.oidfed.DefaultSubordinateStatementTrustEngine">
+ <property name="criteriaSetLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementValidationCriteriaSetLookupFunction"
+ p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"/>
+ </property>
+ </bean>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.subordinateStatement.claimsValidator:shibboleth.oidfed.DefaultSubordinateStatementClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
+ </bean>
+ </util:list>
+ </property>
+ </bean>
+ </property>
+ <property name="fetchStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.subordinate.DefaultSubordinateStatementFetchingStrategy"
+ p:httpClient="#{getObject('shibboleth.oidfed.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+ p:httpClientSecurityParameters="#{getObject('shibboleth.oidfed.NonBrowser.HttpClientSecurityParameters')}"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper"
+ p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache">
+ <property name="criteriaToValidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.subordinateStatement.validContainerLifetime:PT5M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToInvalidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.subordinateStatement.invalidContainerLifetime:PT1M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToExceptionContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.subordinateStatement.exceptionContainerLifetime:PT0S}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="clientAuthenticationLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultClientAuthenticationLookupFunction"
+ p:clientAuthenticationDecoratorsLookupStrategy-ref="shibboleth.oidfed.cache.DefaultEndpointAuthenticationFunctions">
+ <property name="supportedAuthenticationMethodsLookupStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.FederationEndpointEntityStatementCriterion))?.getValue().getParsedPayload().getMetadata()?.getFederationEntityMetadata()?.get('federation_fetch_endpoint_auth_methods')" />
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <util:list id="shibboleth.oidfed.cache.DefaultEndpointAuthenticationFunctions">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultPrivateKeyJwtClientAuthenticationFunction"
+ p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}">
+ <property name="typeHeaderLookupStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#null" />
+ </property>
+ <property name="audienceLookupStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.FederationEndpointEntityStatementCriterion))?.getValue().getSubject()" />
+ </property>
+ <property name="issuerLookupStrategy">
+ <bean parent="shibboleth.Functions.Constant"
+ c:target-ref="shibboleth.oidc.issuer" />
+ </property>
+ <property name="securityConfigurationLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultAuthenticationSecurityConfigurationLookupFunction"
+ p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyResolverService" />
+ </property>
+ <property name="signatureSigningParametersResolver">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jose.impl.EndpointAuthenticationSignatureSigningParametersResolver">
+ <constructor-arg index="0" value="%{idp.oidfed.cache.clientauthentication.jwt.defaultalg:RS256}" />
+ </bean>
+ </property>
+ </bean>
+ </util:list>
+
+
+ <bean id="shibboleth.oidfed.DefaultSubordinateStatementClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.oidfed.cache.subordinateStatement.maxJwtLifetime:PT24H}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="jwks" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.CritClaimsValidator"
+ p:recognizedClaims="%{idp.oidfed.cache.subordinateStatement.critClaims:%{idp.oidfed.cache.default.critClaims:}}" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.MetadataPolicyCritClaimsValidator">
+ <property name="supportedOperatorsLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.DefaultMetadataPolicyOperatorsLookupStrategy"
+ p:defaultSupportedOperators-ref="#{'%{idp.oidfed.authorize.MetadataPolicyOperators:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}">
+ <property name="supportedOperators">
+ <util:map>
+ <entry key="net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest" value-ref="#{'%{idp.oidfed.resolve-entity.MetadataPolicyOperators:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}" />
+ <entry key="net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ExplicitClientRegistrationRequest" value-ref="#{'%{idp.oidfed.register.MetadataPolicyOperators:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}" />
+ </util:map>
+ </property>
+ </bean>
+ </property>
+ </bean>
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="authority_hints" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="trust_anchor_hints" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="trust_marks" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="trust_mark_issuers" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="trust_mark_owners" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="aud" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.ConstraintsSyntaxClaimsValidator"/>
+ </util:list>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.TrustChainMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+ p:criteriaToIdentifierStrategy-ref="shibboleth.oidfed.DefaultSubjectEntityIDCriteriaToIdentifierStrategy"
+ p:minCacheDuration="%{idp.oidfed.trustChain.minRefreshDelay:PT1M}"
+ p:maxCacheDuration="%{idp.oidfed.trustChain.maxRefreshDelay:PT30M}"
+ p:metadataExpirationTimeStrategy-ref="DefaultResponseContainerExpirationTimeStrategy">
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getEntityId()"/>
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustchain.DefaultTrustChainSignatureValidationFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.trustChain.customFilterStrategies:}'.trim())}">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadSignatureValidationCredentialResolver" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="entityStatementSignatureValidationFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultEntityStatementSignatureValidationFilterStrategy">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultLocalTrustAnchorCredentialResolver"
+ c:cache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ <property name="fetchStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustchain.DefaultTrustChainFetchingStrategy"
+ p:criteriaToSubjectEntityIdStrategy-ref="shibboleth.oidfed.DefaultSubjectEntityIDCriteriaToIdentifierStrategy"
+ p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"
+ p:subordinateStatementCache-ref="shibboleth.oidfed.SubordinateEntityStatementMetadataCache"
+ p:localTrustAnchorsCache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache">
+ <property name="criteriaToValidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustChain.validContainerLifetime:PT5M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToInvalidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustChain.invalidContainerLifetime:PT1M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.ResolveEntityTrustChainMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+ p:minCacheDuration="%{idp.oidfed.cache.resolveEntity.minRefreshDelay:PT1M}"
+ p:maxCacheDuration="%{idp.oidfed.cache.resolveEntity.maxRefreshDelay:PT30M}"
+ p:metadataExpirationTimeStrategy-ref="DefaultResponseContainerExpirationTimeStrategy">
+ <property name="criteriaToIdentifierStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.resolver.ResolveEntityCacheIdentifierCriterion))?.getIdentifier()"/>
+ </property>
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getIdentifier()"/>
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementMetadataFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.resolveEntity.customFilterStrategies:}'.trim())}">
+ <property name="validationConditions">
+ <util:list value-type="java.util.function.BiPredicate">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementSignatureValidationCondition"
+ p:trustEngine-ref="shibboleth.oidfed.DefaultSubordinateStatementTrustEngine">
+ <property name="criteriaSetLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementValidationCriteriaSetLookupFunction"
+ p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"/>
+ </property>
+ </bean>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.remoteResolver.claimsValidator:shibboleth.oidfed.DefaultResolveEntityResponseClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
+ </property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
+ </bean>
+ </util:list>
+ </property>
+ </bean>
+ </property>
+ <property name="fetchStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.resolver.DefaultResolveEntityTrustChainFetchingStrategy"
+ p:httpClient="#{getObject('shibboleth.oidfed.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+ p:httpClientSecurityParameters="#{getObject('shibboleth.oidfed.NonBrowser.HttpClientSecurityParameters')}"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper">
+ <property name="criteriaToValidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.resolveEntity.validContainerLifetime:PT5M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToInvalidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.resolveEntity.invalidContainerLifetime:PT1M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToExceptionContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.resolveEntity.exceptionContainerLifetime:PT0S}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="clientAuthenticationLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultClientAuthenticationLookupFunction"
+ p:clientAuthenticationDecoratorsLookupStrategy-ref="shibboleth.oidfed.cache.DefaultEndpointAuthenticationFunctions">
+ <property name="supportedAuthenticationMethodsLookupStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.FederationEndpointEntityStatementCriterion))?.getValue().getParsedPayload().getMetadata()?.getFederationEntityMetadata()?.get('federation_resolve_entity_endpoint_auth_methods')" />
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultResolveEntityResponseClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.oidfed.cache.resolveEntity.maxJwtLifetime:PT24H}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="metadata" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="trust_chain" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.TrustMarksClaimsValidator" />
+ </util:list>
+ </property>
+ </bean>
+
+ <util:map id="shibboleth.oidfed.DefaultFederationPolicyConstraints"
+ value-type="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.constraints.FederationPolicyConstraint">
+ <entry key="max_path_length">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.constraints.impl.DefaultMaxPathLengthConstraint" />
+ </entry>
+ <entry key="naming_constraints">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.constraints.impl.DefaultNamingConstraintsConstraint" />
+ </entry>
+ <entry key="allowed_entity_types">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.constraints.impl.DefaultAllowedEntityTypesConstraint"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper"/>
+ </entry>
+ </util:map>
+
+ <bean id="shibboleth.oidfed.DefaultLocalTrustAnchorsFilename" class="java.lang.String" factory-method="valueOf">
+ <constructor-arg value="%{idp.oidfed.LocalTrustAnchorsFile:%{idp.home}/conf/oidfed/oidfed-trust-anchors.json}" />
+ </bean>
+
+ <bean id="shibboleth.oidfed.LocalTrustAnchorsMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilderSpec"
+ p:criteriaToIdentifierStrategy-ref="shibboleth.oidfed.LocalTrustAnchorsDefaultIdentifierStrategy"
+ p:identifierExtractionStrategy-ref="shibboleth.oidfed.LocalTrustAnchorsDefaultIdentifierStrategy"
+ p:metadataValidPredicate="#{getObject('shibboleth.oidfed.LocalTrustAnchorsValidator') ?: getObject('shibboleth.oidfed.DefaultLocalTrustAnchorsValidator')}">
+ <property name="loadingStrategy">
+ <bean class="net.shibboleth.oidc.metadata.cache.impl.DefaultResourceLoadingStrategy">
+ <constructor-arg name="metadata">
+ <bean class="org.springframework.core.io.Resource"
+ factory-bean="PreferFileSystemResourceLoader" factory-method="getResource">
+ <constructor-arg>
+ <ref bean="shibboleth.oidfed.DefaultLocalTrustAnchorsFilename" />
+ </constructor-arg>
+ </bean>
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="parsingStrategy">
+ <bean class="net.shibboleth.oidc.metadata.cache.impl.DefaultJSONMapParsingStrategy"
+ c:mapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper"
+ c:valueClass="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.LocalKeyContainer"/>
+ </property>
+ <property name="sourceMetadataExpiryStrategy">
+ <bean class="net.shibboleth.oidc.metadata.cache.impl.DefaultSourceMetadataExpirationTimeStrategy"
+ c:duration="PT10M"/>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.TrustMarkStatusMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultTrustMarkStatusMetadataCache" parent="shibboleth.oidfed.TrustMarkStatusMetadataCacheBuilderSpec"
+ p:cleanupTaskInterval="PT30S"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.TrustMarkStatusMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+ p:minCacheDuration="%{idp.oidfed.cache.trustMarkStatus.maxRefreshDelay:PT60S}"
+ p:maxCacheDuration="%{idp.oidfed.cache.trustMarkStatus.maxRefreshDelay:PT30M}"
+ p:metadataExpirationTimeStrategy-ref="DefaultResponseContainerExpirationTimeStrategy">
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getIdentifier()"/>
+ </property>
+ <property name="criteriaToIdentifierStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.TrustMarkStatusIdentifierCriterion))?.getIdentifier()"/>
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementMetadataFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.trustMarkStatus.customFilterStrategies:}'.trim())}">
+ <property name="validationConditions">
+ <util:list value-type="java.util.function.BiPredicate">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementSignatureValidationCondition">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultTrustMarkStatusCredentialResolver" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaSetLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.DefaultTrustMarkValidationCriteriaSetLookupFunction"
+ p:trustChainCache-ref="shibboleth.oidfed.TrustChainMetadataCache"/>
+ </property>
+ </bean>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.trustMarkStatus.claimsValidator:shibboleth.oidfed.DefaultTrustMarkStatusClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
+ </property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getIssuer())"/>
+ </property>
+ </bean>
+ </util:list>
+ </property>
+ </bean>
+ </property>
+ <property name="fetchStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.DefaultTrustMarkStatusFetchingStrategy"
+ p:httpClient="#{getObject('shibboleth.oidfed.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+ p:httpClientSecurityParameters="#{getObject('shibboleth.oidfed.NonBrowser.HttpClientSecurityParameters')}"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper">
+ <property name="criteriaToValidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustMarkStatus.validContainerLifetime:PT5M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToInvalidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustMarkStatus.invalidContainerLifetime:PT1M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToExceptionContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustMarkStatus.exceptionContainerLifetime:PT0S}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="clientAuthenticationLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultClientAuthenticationLookupFunction"
+ p:clientAuthenticationDecoratorsLookupStrategy-ref="shibboleth.oidfed.cache.DefaultEndpointAuthenticationFunctions">
+ <property name="supportedAuthenticationMethodsLookupStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.FederationEndpointEntityStatementCriterion))?.getValue()?.getParsedPayload()?.getMetadata()?.getFederationEntityMetadata()?.get('federation_trust_mark_status_endpoint_auth_methods')" />
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultTrustMarkStatusClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.oidfed.cache.trustMarkStatus.maxJwtLifetime:PT24H}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="trust_mark" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="status" />
+ </util:list>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.TrustMarkMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultTrustMarkMetadataCache" parent="shibboleth.oidfed.TrustMarkMetadataCacheBuilderSpec"
+ p:cleanupTaskInterval="PT30S"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.TrustMarkMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+ p:minCacheDuration="%{idp.oidfed.cache.trustMark.minRefreshDelay:PT1H}"
+ p:maxCacheDuration="%{idp.oidfed.cache.trustMark.maxRefreshDelay:PT24H}"
+ p:metadataExpirationTimeStrategy-ref="DefaultResponseContainerExpirationTimeStrategy">
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getIdentifier()"/>
+ </property>
+ <property name="criteriaToIdentifierStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.TrustMarkIdentifierCriterion))?.getIdentifier()"/>
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementMetadataFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.trustMark.customFilterStrategies:}'.trim())}">
+ <property name="validationConditions">
+ <util:list value-type="java.util.function.BiPredicate">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementSignatureValidationCondition"
+ p:trustEngine-ref="shibboleth.oidfed.DefaultSubordinateStatementTrustEngine">
+ <property name="criteriaSetLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.DefaultTrustMarkValidationCriteriaSetLookupFunction"
+ p:trustChainCache-ref="shibboleth.oidfed.TrustChainMetadataCache"/>
+ </property>
+ </bean>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.trustMark.claimsValidator:shibboleth.oidfed.DefaultTrustMarkClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
+ </property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
+ </bean>
+ </util:list>
+ </property>
+ </bean>
+ </property>
+ <property name="fetchStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.DefaultTrustMarkFetchingStrategy"
+ p:httpClient="#{getObject('shibboleth.oidfed.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+ p:httpClientSecurityParameters="#{getObject('shibboleth.oidfed.NonBrowser.HttpClientSecurityParameters')}"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper">
+ <property name="criteriaToValidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustMark.validContainerLifetime:PT5M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToInvalidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustMark.invalidContainerLifetime:PT1M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToExceptionContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustMark.exceptionContainerLifetime:PT0S}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="clientAuthenticationLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultClientAuthenticationLookupFunction"
+ p:clientAuthenticationDecoratorsLookupStrategy-ref="shibboleth.oidfed.cache.DefaultEndpointAuthenticationFunctions">
+ <property name="supportedAuthenticationMethodsLookupStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.FederationEndpointEntityStatementCriterion))?.getValue().getParsedPayload().getMetadata()?.getFederationEntityMetadata()?.get('federation_trust_mark_endpoint_auth_methods')" />
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultTrustMarkClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.oidfed.maxTrustMarkifetime:P365D}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="trust_mark_type" />
+ </util:list>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultDelegatedTrustMarkTrustEngine"
+ class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultTrustMarkOwnerCredentialResolver" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.SignedKeysetMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+ p:minCacheDuration="%{idp.oidfed.cache.signedKeyset.minRefreshDelay:PT1M}"
+ p:maxCacheDuration="%{idp.oidfed.cache.signedKeyset.maxRefreshDelay:PT30M}"
+ p:metadataExpirationTimeStrategy-ref="DefaultResponseContainerExpirationTimeStrategy">
+ <property name="criteriaToIdentifierStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityStatementCriterion))?.getValue().getSubject() + ':' + #input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.keyset.SubjectSignedKeysetUriCriterion))?.getValue()"/>
+ </property>
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getEntityId() + ':' + #input?.getIdentifier().getUri()"/>
+ </property>
+ <property name="fetchStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.keyset.DefaultSignedKeysetFetchingStrategy"
+ p:httpClient="#{getObject('shibboleth.oidfed.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+ p:httpClientSecurityParameters="#{getObject('shibboleth.oidfed.NonBrowser.HttpClientSecurityParameters')}"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper">
+ <property name="criteriaToValidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.signedKeyset.validContainerLifetime:PT5M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToInvalidContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.signedKeyset.invalidContainerLifetime:PT1M}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="criteriaToExceptionContainerLifetimeStrategy">
+ <bean parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.signedKeyset.exceptionContainerLifetime:PT0S}" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementMetadataFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.signedKeyset.customFilterStrategies:}'.trim())}">
+ <property name="validationConditions">
+ <util:list value-type="java.util.function.BiPredicate">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementSignatureValidationCondition"
+ p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine">
+ <property name="criteriaSetLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.keyset.DefaultSignedKeysetCriteriaSetLookupFunction"/>
+ </property>
+ </bean>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.signedKeyset.claimsValidator:shibboleth.oidfed.DefaultSignedKeysetClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
+ </bean>
+ </util:list>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultSignedKeysetClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.oidfed.cache.entityConfiguration.maxJwtLifetime:PT24H}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="keys" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ </util:list>
+ </property>
+ </bean>
+
+ <bean id="PreferFileSystemResourceLoader"
+ class="net.shibboleth.shared.spring.resource.PreferFileSystemResourceLoader"/>
+
+ <bean id="shibboleth.oidfed.DefaultLocalTrustAnchorsValidator"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultLocalTrustAnchorsValidator"/>
+
+ <bean id="shibboleth.oidfed.LocalTrustAnchorsDefaultIdentifierStrategy"
+ parent="shibboleth.Functions.Constant" c:target="#{getObject('shibboleth.oidfed.DefaultLocalTrustAnchorsFilename') ?: 'undefined'}" />
+
+
+ <bean id="shibboleth.oidfed.JWTPayloadJSONObjectMapper" parent="shibboleth.oidc.JSONObjectMapper" />
+ <bean id="shibboleth.oidfed.policy.JSONSimpleModule" class="com.fasterxml.jackson.databind.module.SimpleModule"/>
+ <bean id="shibboleth.oidfed.JacksonSimpleTypeResolver" class="com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver"/>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.policy.JSONSimpleModule" />
+ <property name="targetMethod" value="addDeserializer" />
+ <property name="arguments">
+ <list>
+ <value>#{ T(net.shibboleth.oidc.metadata.policy.MetadataPolicy)}</value>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.FederationMetadataPolicyDeserializer"
+ c:_0="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.policy.JSONSimpleModule" />
+ <property name="targetMethod" value="addDeserializer" />
+ <property name="arguments">
+ <list>
+ <value>#{ T(com.nimbusds.jose.jwk.JWKSet)}</value>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.jackson.JWKSetDeserializer"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.policy.JSONSimpleModule" />
+ <property name="targetMethod" value="addDeserializer" />
+ <property name="arguments">
+ <list>
+ <value>#{ T(com.nimbusds.jose.jwk.JWK)}</value>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.jackson.JWKDeserializer"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.policy.JSONSimpleModule" />
+ <property name="targetMethod" value="addDeserializer" />
+ <property name="arguments">
+ <list>
+ <value>#{ T(java.time.Instant)}</value>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.jackson.InstantDeserializer"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.JacksonSimpleTypeResolver" />
+ <property name="targetMethod" value="addMapping" />
+ <property name="arguments">
+ <list>
+ <value>#{ T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.Metadata)}</value>
+ <value>#{ T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.impl.MetadataImpl)}</value>
+ </list>
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.JacksonSimpleTypeResolver" />
+ <property name="targetMethod" value="addMapping" />
+ <property name="arguments">
+ <list>
+ <value>#{ T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.TrustMarkOwner)}</value>
+ <value>#{ T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.impl.TrustMarkOwnerImpl)}</value>
+ </list>
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.policy.JSONSimpleModule" />
+ <property name="targetMethod" value="setAbstractTypes" />
+ <property name="arguments">
+ <list>
+ <ref bean="shibboleth.oidfed.JacksonSimpleTypeResolver" />
+ </list>
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ <property name="targetMethod" value="registerModule" />
+ <property name="arguments">
+ <list>
+ <ref bean="shibboleth.oidfed.policy.JSONSimpleModule" />
+ </list>
+ </property>
+ </bean>
+
+ <util:list id="shibboleth.oidfed.StandardMetadataPolicyOperators">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyValueOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyAddOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyDefaultOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyOneOfOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicySubsetOfOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicySupersetOfOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEssentialOperator"/>
+ </util:list>
+
+ <bean p:id="oidc/metadata-lookup-ext/oidfed" parent="shibboleth.oidc.MetadataLookupExtensionFlow" />
+
+ <bean id="shibboleth.oidc.DefaultResolveEntityApiMappedErrors"
+ parent="shibboleth.oidc.DefaultApiMappedErrors"
+ class="org.springframework.beans.factory.config.MapFactoryBean">
+ <property name="sourceMap">
+ <map merge="true" value-type="com.nimbusds.oauth2.sdk.ErrorObject">
+ <entry>
+ <key>
+ <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MSG_CTX"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="server_error" c:_1="Internal server error" c:_2="500" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_TRUST_ANCHOR"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_trust_anchor" c:_1="Trust anchor in the request is invalid" c:_2="404" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_SUBJECT"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_subject" c:_1="Subject in the request is invalid" c:_2="404" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_METADATA"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_metadata" c:_1="Metadata is invalid or not found for the requested entity types" c:_2="400" />
+ </entry>
+ </map>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.register.DefaultMappedErrors"
+ parent="shibboleth.oidc.register.DefaultMappedErrors"
+ class="org.springframework.beans.factory.config.MapFactoryBean">
+ <property name="sourceMap">
+ <map value-type="com.nimbusds.oauth2.sdk.ErrorObject">
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.NO_TRUST_CHAINS_RESOLVED"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_client" c:_1="No acceptable trust chains could be resolved for the client" c:_2="401" />
+ </entry>
+ </map>
+ </property>
+ </bean>
+
+ <bean id="DefaultTrustMarkClaimsValidationLookupStrategy" parent="shibboleth.Functions.Constant"
+ c:target-ref="shibboleth.oidfed.DefaultTrustMarkClaimsValidator" />
+
+ <bean id="DefaultDelegatedTrustMarkClaimsValidationLookupStrategy" parent="shibboleth.Functions.Constant">
+ <constructor-arg name="target">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.oidfed.maxTrustMarkifetime:P365D}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="trust_mark_type" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ </util:list>
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+
+
+ <bean id="shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultPreSelectedTrustChainIDsLookupStrategy">
+ <property name="profileIdLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataLookupExtensionContextProfileIDLookupFunction"/>
+ </property>
+ <property name="profileSpecificStrategies">
+ <util:map value-type="java.util.function.Function">
+ <entry key="#{T(net.shibboleth.oidc.profile.config.OIDCSSOProfileConfiguration).PROFILE_ID}">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy"/>
+ </entry>
+ </util:map>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultProvidedTrustChainResolver"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.DefaultProvidedTrustChainResolver">
+ <property name="profileIdLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataLookupExtensionContextProfileIDLookupFunction"/>
+ </property>
+ <property name="profileSpecificResolvers">
+ <util:map value-type="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.ProvidedTrustChainResolver">
+ <entry key="#{T(net.shibboleth.oidc.profile.config.OIDCSSOProfileConfiguration).PROFILE_ID}">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultAuthorizationRequestObjectProvidedTrustChainResolver"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </entry>
+ <entry key="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2PushedAuthorizationRequestConfiguration).PROFILE_ID}">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultPushedAuthorizationRequestObjectProvidedTrustChainResolver"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </entry>
+ </util:map>
+ </property>
+ </bean>
+
+ <util:map id="DefaultAutoRegisteredTokenClaimsSetDecorators" value-type="java.util.List">
+ <entry key="#{null}">
+ <util:list>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainIDsClaimsSetDecorator"/>
+ </util:list>
+ </entry>
+ <entry key="store">
+ <util:list>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainIDsClaimsSetDecorator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredClientInformationStoreClaimsSetDecorator"
+ p:clientInformationManager-ref="#{'%{idp.oidfed.autoreg.clientInformationManager:shibboleth.oidc.ClientInformationManager}'.trim()}" />
+ </util:list>
+ </entry>
+ </util:map>
+
+ <bean id="AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator"
+ p:claimsSetDecorators-ref="DefaultAutoRegisteredTokenClaimsSetDecorators"/>
+
+ <bean id="AutoRegisteredTrustChainAccessTokenClaimsSetDecorator"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainAccessTokenClaimsSetDecorator"
+ p:claimsSetDecorators-ref="DefaultAutoRegisteredTokenClaimsSetDecorators"/>
+
+ <bean id="AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator"
+ p:claimsSetDecorators-ref="DefaultAutoRegisteredTokenClaimsSetDecorators"/>
+
+
+ <bean id="AutoRegisteredTrustChainRequestUriClaimsSetDecorator"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainRequestUriClaimsSetDecorator"
+ p:claimsSetDecorators-ref="DefaultAutoRegisteredTokenClaimsSetDecorators"/>
+
+ <bean id="ClientInformationSignedKeysetJWKSetDecorator"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.ClientInformationSignedKeysetJWKSetDecorator"
+ p:signedKeysetCache-ref="#{'%{idp.oidfed.SignedKeysetMetadataCache:shibboleth.oidfed.SignedKeysetMetadataCache}'.trim()}" />
+
+ <bean id="AutomaticRegistrationCondition"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultAutomaticRegistrationActivationCondition">
+ <property name="profileIdLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataLookupExtensionContextProfileIDLookupFunction"/>
+ </property>
+ <property name="profileConditions">
+ <util:map value-type="java.util.function.Predicate">
+ <entry key="#{T(net.shibboleth.oidc.profile.config.OIDCSSOProfileConfiguration).PROFILE_ID}">
+ <ref bean="shibboleth.oidfed.authorize.DefaultAutomaticRegistrationCondition"/>
+ </entry>
+ <entry key="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2PushedAuthorizationRequestConfiguration).PROFILE_ID}">
+ <ref bean="shibboleth.oidfed.par.DefaultAutomaticRegistrationCondition"/>
+ </entry>
+ <entry key="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenConfiguration).PROFILE_ID}">
+ <ref bean="shibboleth.oidfed.token.DefaultAutomaticRegistrationCondition"/>
+ </entry>
+ <entry key="#{T(net.shibboleth.oidc.profile.config.OIDCUserInfoConfiguration).PROFILE_ID}">
+ <ref bean="shibboleth.oidfed.userinfo.DefaultAutomaticRegistrationCondition"/>
+ </entry>
+ <entry key="#{T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationResolveEntityProfileConfiguration).PROFILE_ID}">
+ <ref bean="shibboleth.oidfed.resolve-entity.DefaultAutomaticRegistrationCondition"/>
+ </entry>
+ </util:map>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidfed.authorize.DefaultAutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <list>
+ <ref bean="%{idp.oidfed.authorize.automaticRegistrationCondition:shibboleth.Conditions.FALSE}"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage() instanceof T(com.nimbusds.oauth2.sdk.AuthorizationRequest) and #input.ensureInboundMessageContext().getMessage().specifiesRequestObject()" />
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="!(#input.ensureInboundMessageContext().getMessage().getRequestObject() instanceof T(com.nimbusds.jwt.PlainJWT))" />
+ <bean parent="shibboleth.Conditions.OR">
+ <constructor-arg>
+ <list>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="!(#input.ensureInboundMessageContext().getMessage().getRequestObject() instanceof T(com.nimbusds.jwt.SignedJWT))" />
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage().getClientID().getValue().equals(#input.ensureInboundMessageContext().getMessage().getRequestObject().getJWTClaimsSet().getClaim('client_id'))" />
+ </list>
+ </constructor-arg>
+ </bean>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.token.DefaultAutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <list>
+ <ref bean="%{idp.oidfed.token.automaticRegistrationCondition:shibboleth.Conditions.FALSE}"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage() instanceof T(com.nimbusds.oauth2.sdk.TokenRequest)" />
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.getOutboundMessageContext() != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)) != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAuthorizationGrantClaimsSet() != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAuthorizationGrantClaimsSet().getClaimsSet().getClaim(T(net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport).KEY_AUTO_REGISTERED_TRUST_CHAIN) != null"/>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.par.DefaultAutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <list>
+ <ref bean="%{idp.oidfed.par.automaticRegistrationCondition:shibboleth.Conditions.FALSE}"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage() instanceof T(com.nimbusds.oauth2.sdk.PushedAuthorizationRequest)" />
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="!(#input.ensureInboundMessageContext().getMessage().getAuthorizationRequest().getRequestObject() instanceof T(com.nimbusds.jwt.PlainJWT))" />
+ <bean parent="shibboleth.Conditions.OR">
+ <constructor-arg>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage().getAuthorizationRequest().specifiesRequestObject()" />
+ </constructor-arg>
+ <constructor-arg>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage().getClientAuthentication() != null and #input.ensureInboundMessageContext().getMessage().getClientAuthentication().getMethod().equals(T(com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod).PRIVATE_KEY_JWT)" />
+ </constructor-arg>
+ </bean>
+ <bean parent="shibboleth.Conditions.OR">
+ <constructor-arg>
+ <list>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="!(#input.ensureInboundMessageContext().getMessage().getAuthorizationRequest().getRequestObject() instanceof T(com.nimbusds.jwt.SignedJWT))" />
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage().getAuthorizationRequest().getClientID().getValue().equals(#input.ensureInboundMessageContext().getMessage().getAuthorizationRequest().getRequestObject().getJWTClaimsSet().getClaim('client_id'))" />
+ </list>
+ </constructor-arg>
+ </bean>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.userinfo.DefaultAutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <list>
+ <ref bean="%{idp.oidfed.userinfo.automaticRegistrationCondition:shibboleth.Conditions.FALSE}"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage() instanceof T(com.nimbusds.openid.connect.sdk.UserInfoRequest)" />
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.getOutboundMessageContext() != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)) != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAuthorizationGrantClaimsSet() != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAuthorizationGrantClaimsSet().getClaimsSet().getClaim(T(net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport).KEY_AUTO_REGISTERED_TRUST_CHAIN) != null"/>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.resolve-entity.DefaultAutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <list>
+ <ref bean="%{idp.oidfed.resolve-entity.automaticRegistrationCondition:shibboleth.Conditions.TRUE}"/>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ <bean class="net.shibboleth.idp.plugin.oidc.op.security.jwt.claims.RequestObjectClaimsValidator">
+ <constructor-arg>
+ <bean id="shibboleth.oidfed.DefaultRequestObjectClaimsValidation"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+ p:claimValidators-ref="FederationClaimsValidators" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.RequestObjectValidationCondition"
+ parent="shibboleth.BiConditions.Expression"
+ c:expression="#input1.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext)) and #input1.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext)).getSelectedTrustChain() != null"/>
+
+ <util:list id="FederationClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator"
+ p:extraAudienceValidation="true"
+ p:activationCondition-ref="shibboleth.oidfed.RequestObjectValidationCondition">
+ <property name="audienceLookupStrategy">
+ <bean parent="shibboleth.BiFunctions.Expression"
+ c:expression="#custom.apply(#input1)"
+ p:customObject-ref="shibboleth.ResponderIdLookup.Simple" />
+ </property>
+ </bean>
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.JWTIdentifierClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:activationCondition-ref="shibboleth.oidfed.RequestObjectValidationCondition"
+ p:replayCache-ref="shibboleth.ReplayCache" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:activationCondition-ref="shibboleth.oidfed.RequestObjectValidationCondition"
+ p:requiredClaims="exp" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:activationCondition-ref="shibboleth.oidfed.RequestObjectValidationCondition"
+ p:prohibitedClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:activationCondition-ref="shibboleth.oidfed.RequestObjectValidationCondition"
+ p:claimName="client_id">
+ <property name="valueToMatchLookupStrategy">
+ <bean parent="shibboleth.BiFunctions.Expression"
+ c:expression="#custom.apply(#input1.getInboundMessageContext()) == null ? null : #custom.apply(#input1.getInboundMessageContext()).toString()">
+ <property name="customObject">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataLookupExtensionContextClientIDLookupFunction" />
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </util:list>
+
+ <bean parent="shibboleth.oidc.PushedAuthorizationErrorMapping">
+ <property name="eventId">
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_METADATA_POLICY"/>
+ </property>
+ <property name="errorObject">
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_metadata" c:_1="Merged metadata policy is invalid" c:_2="400" />
+ </property>
+ </bean>
+
+ <bean parent="shibboleth.oidc.PushedAuthorizationErrorMapping">
+ <property name="eventId">
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_METADATA_AGAINST_POLICY"/>
+ </property>
+ <property name="errorObject">
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_metadata" c:_1="Requested metadata is not compliant with the merged policy" c:_2="400" />
+ </property>
+ </bean>
+
+ <bean parent="shibboleth.oidc.PushedAuthorizationErrorMapping">
+ <property name="eventId">
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.NO_TRUST_CHAINS_RESOLVED"/>
+ </property>
+ <property name="errorObject">
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_client" c:_1="No acceptable trust chains could be resolved for the client" c:_2="401" />
+ </property>
+ </bean>
+
+ <alias alias="UseResolverApiCondition" name="%{idp.oidfed.trustchain.resolver.useResolverApiCondition:shibboleth.Conditions.FALSE}" />
+ <alias alias="FallbackToLocalResolutionCondition" name="%{idp.oidfed.trustchain.resolver.fallbackToLocalCondition:shibboleth.Conditions.TRUE}" />
+
+ <bean id="shibboleth.oidfed.RemoteTrustMark" abstract="true"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.DefaultTrustMarkFromMetadataCacheFetchingFunction"
+ p:subject-ref="shibboleth.oidc.issuer"
+ p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"
+ p:trustMarkCache-ref="shibboleth.oidfed.TrustMarkMetadataCache"/>
+
+ <bean id="shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.oidfed.cache.explicitRegistration.maxJwtLifetime:PT1M}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="jwks" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
+ <property name="audienceLookupStrategy">
+ <bean parent="shibboleth.BiFunctions.Constant"
+ c:target-ref="shibboleth.oidc.issuer" />
+ </property>
+ </bean>
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="metadata_policy" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="metadata_policy_crit" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="constraints" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="source_endpoint" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.CritClaimsValidator"
+ p:recognizedClaims="%{idp.oidfed.cache.explicitRegistration.critClaims:%{idp.oidfed.cache.default.critClaims:}}" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="authority_hints" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.NonEmptyStringArrayClaimsValidator"
+ p:nonEmptyArrayClaims="authority_hints,trust_anchor_hints" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.TrustMarksClaimsValidator" />
+ </util:list>
+ </property>
+ </bean>
+
+ <!-- Property-based definition of login flows for the resolve-entity endpoint. -->
+ <bean id="shibboleth.oidfed.resolver.PotentialFlows" class="org.springframework.beans.factory.config.ListFactoryBean"
+ p:sourceList="#{getObject('shibboleth.AuthenticationFlowDescriptorManager').getComponents().?[id matches 'authn/(' + '%{idp.oidfed.resolve-entity.authn.flows:OAuth2Client}'.trim() + ')']}" />
+
+ <import resource="${idp.home}/conf/oidfed/oidfed-trustchain-resolver.xml"/>
+
+</beans>
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/resources/META-INF/services/net.shibboleth.idp.module.IdPModule b/oidfed-common-impl/src/main/resources/META-INF/services/net.shibboleth.idp.module.IdPModule
new file mode 100644
index 0000000..07865f9
--- /dev/null
+++ b/oidfed-common-impl/src/main/resources/META-INF/services/net.shibboleth.idp.module.IdPModule
@@ -0,0 +1 @@
+net.shibboleth.oidfed.OIDFedCommonModule
diff --git a/oidfed-common-impl/src/main/resources/META-INF/services/net.shibboleth.idp.plugin.IdPPlugin b/oidfed-common-impl/src/main/resources/META-INF/services/net.shibboleth.idp.plugin.IdPPlugin
new file mode 100644
index 0000000..e0042ba
--- /dev/null
+++ b/oidfed-common-impl/src/main/resources/META-INF/services/net.shibboleth.idp.plugin.IdPPlugin
@@ -0,0 +1,2 @@
+net.shibboleth.oidfed.OIDFedCommonPlugin
+
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/resources/net/shibboleth/oidfed/module.properties b/oidfed-common-impl/src/main/resources/net/shibboleth/oidfed/module.properties
new file mode 100644
index 0000000..230ef99
--- /dev/null
+++ b/oidfed-common-impl/src/main/resources/net/shibboleth/oidfed/module.properties
@@ -0,0 +1,9 @@
+# Class to Module ID mappings
+net.shibboleth.oidfed.OIDFedCommonModule = idp.oidfed.common
+
+# Module Owner
+idp.oidfed.common.plugin = net.shibboleth.oidfed.common
+
+idp.oidfed.common.name = OIDFed Common Library
+idp.oidfed.common.desc = Module that provides common OpenID Federation processing functions for other plugins to use
+idp.oidfed.common.url = /OIDFedCommon
diff --git a/oidfed-common-impl/src/main/resources/net/shibboleth/oidfed/plugin.properties b/oidfed-common-impl/src/main/resources/net/shibboleth/oidfed/plugin.properties
new file mode 100644
index 0000000..899a876
--- /dev/null
+++ b/oidfed-common-impl/src/main/resources/net/shibboleth/oidfed/plugin.properties
@@ -0,0 +1,6 @@
+plugin.id = net.shibboleth.oidfed.common
+# Only used when package manifest is not available
+plugin.version = 1.0.0
+#plugin.license = licence.txt
+
+plugin.modules.required = idp.oidc.common.8
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..f66d436
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.shibboleth</groupId>
+ <artifactId>parent</artifactId>
+ <version>17.0.2</version>
+ </parent>
+
+ <groupId>net.shibboleth.oidfed</groupId>
+ <artifactId>oidfed-common-parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <name>Shibboleth IdP :: Plugins :: OpenID Federation Common Library :: Parent</name>
+ <packaging>pom</packaging>
+ <description>A POM containing properties, dependencies, etc. that are common across all Shibboleth OIDFed Java common libraries.</description>
+
+ <properties>
+ <shibboleth.projectName>java-oidfed-common</shibboleth.projectName>
+ <idp.groupId>net.shibboleth.idp</idp.groupId>
+ <idp.version>5.0.0</idp.version>
+ <opensaml.groupId>org.opensaml</opensaml.groupId>
+ <opensaml.version>5.0.0</opensaml.version>
+ <shib-shared.testing.version>9.0.0</shib-shared.testing.version>
+ <oidc-config.groupId>net.shibboleth.idp.plugin.config.oidc</oidc-config.groupId>
+ <oidc-config.version>3.1.0-SNAPSHOT</oidc-config.version>
+ <oidc-common.groupId>net.shibboleth.oidc</oidc-common.groupId>
+ <oidc-common.version>3.4.0-SNAPSHOT</oidc-common.version>
+ <oidfed-common.groupId>net.shibboleth.oidfed</oidfed-common.groupId>
+ <oidfed-common.version>1.0.0-SNAPSHOT</oidfed-common.version>
+ <shib-shared.groupId>net.shibboleth</shib-shared.groupId>
+ <shib-shared.version>9.0.0</shib-shared.version>
+ <shib-profile.groupId>net.shibboleth</shib-profile.groupId>
+ <shib-profile.version>5.0.0</shib-profile.version>
+ <shib-attribute.groupId>net.shibboleth</shib-attribute.groupId>
+ <shib-attribute.version>5.0.0</shib-attribute.version>
+ <shib-metadata.groupId>net.shibboleth</shib-metadata.groupId>
+ <shib-metadata.version>5.0.0</shib-metadata.version>
+ <commons.io.version>2.6</commons.io.version>
+ <checkstyle.configLocation>${project.basedir}/resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
+ </properties>
+
+ <modules>
+ <module>oidfed-common-api</module>
+ <module>oidfed-common-impl</module>
+ <module>oidfed-common-dist</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>${slf4j.groupId}</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Project wide test Dependencies -->
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <dependencyManagement>
+ <!-- java-oidfed-common project dependencies -->
+ <dependencies>
+ <dependency>
+ <groupId>${oidfed-common.groupId}</groupId>
+ <artifactId>oidfed-common-api</artifactId>
+ <version>${oidfed-common.version}</version>
+ </dependency>
+ <!-- OIDC Shared config, which is a runtime dependency to the whole project -->
+ <dependency>
+ <groupId>${oidc-config.groupId}</groupId>
+ <artifactId>idp-plugin-oidc-config-impl</artifactId>
+ <version>${oidc-config.version}</version>
+ </dependency>
+ <!-- OIDC Common BOM when importing OIDC dependencies -->
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-bom</artifactId>
+ <version>${oidc-common.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <!-- Import Dependencies: OP/Shib-Metadata/Attribute/Profile/Shared -->
+ <dependency>
+ <groupId>${shib-shared.groupId}</groupId>
+ <artifactId>shib-shared-bom</artifactId>
+ <version>${shib-shared.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>${shib-profile.groupId}</groupId>
+ <artifactId>shib-profile-bom</artifactId>
+ <version>${shib-profile.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>${shib-attribute.groupId}</groupId>
+ <artifactId>shib-attribute-bom</artifactId>
+ <version>${shib-attribute.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>${shib-metadata.groupId}</groupId>
+ <artifactId>shib-metadata-bom</artifactId>
+ <version>${shib-metadata.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <!-- Shibboleth IdP BOM for importing IdP dependencies -->
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-bom</artifactId>
+ <version>${idp.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>${oidc-common.groupId}</groupId>
+ <artifactId>oidc-common-crypto-impl</artifactId>
+ <version>${oidc-common.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.nashorn</groupId>
+ <artifactId>nashorn-core</artifactId>
+ <version>${nashorn.jdk.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons.io.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.shibboleth.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <links combine.children="append">
+ <link>${javadoc.url}/java-shib-shared/${shib-shared.version}</link>
+ <link>${javadoc.url}/java-opensaml/${opensaml.version}</link>
+ <link>${javadoc.url}/java-shib-metadata/${shib-metadata.version}</link>
+ <link>${javadoc.url}/java-shib-attribute/${shib-attribute.version}</link>
+ <link>${javadoc.url}/java-shib-profile/${shib-profile.version}</link>
+ <link>${javadoc.url}/java-identity-provider/${idp.version}</link>
+ <link>${javadoc.url}/java-oidc-common/${oidc-common.version}</link>
+ </links>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Automatic-Module-Name>${automatic.module.name}</Automatic-Module-Name>
+ <Implementation-Title>${project.artifactId}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>shibboleth.net</Implementation-Vendor>
+ <Sealed>true</Sealed>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list