[java-idp-plugin-oidc-op-oidfed] branch dev/CACHE-REFACTOR updated: Improved parsing and validation of entity statements
Codeberg
noreply at shibboleth.net
Mon Feb 9 16:38:56 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/CACHE-REFACTOR
in repository java-idp-plugin-oidc-op-oidfed.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/0d154fc2f0144240bb5599cd31cae648ed7194d7
The following commit(s) were added to refs/heads/dev/CACHE-REFACTOR by this push:
new 0d154fc Improved parsing and validation of entity statements
0d154fc is described below
commit 0d154fc2f0144240bb5599cd31cae648ed7194d7
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Mon Feb 9 18:38:28 2026 +0200
Improved parsing and validation of entity statements
- TrustMarkOwner simplifies Jackson parsing of entity configurations
- MetadataImpl doesn't allow null values as mandated by the spec
- Possible null values after policy enforcer are removed
- New JWT claims validators
- Verify non-empty requirements for authority hints and trust anchor hints
- Verify syntax for constraints and trust_mark_owners
- Improved testing
---
.../payload/EntityConfigurationPayload.java | 4 +-
.../metadata/payload/claim/TrustMarkOwner.java | 49 +++++
.../metadata/cache/TrustMarkOwnersCriterion.java | 7 +-
...DefaultEntityConfigurationFetchingStrategy.java | 4 +-
...efaultSubordinateStatementFetchingStrategy.java | 4 +-
.../impl/AbstractFederationPolicyConstraint.java | 7 +-
.../metadata/payload/claim/impl/MetadataImpl.java | 41 +++-
.../payload/claim/impl/TrustMarkOwnerImpl.java | 120 +++++++++++
.../impl/EntityConfigurationPayloadImpl.java | 7 +-
.../impl/AbstractTrustChainResolutionAction.java | 7 +-
.../op/oidfed/profile/impl/ResolveTrustMarks.java | 13 +-
...tChainTrustedTrustMarkOwnersLookupStrategy.java | 7 +-
.../DefaultTrustMarkOwnerCredentialResolver.java | 20 +-
.../impl/ConstraintsSyntaxClaimsValidator.java | 100 ++++++++++
.../impl/NonEmptyStringArrayClaimsValidator.java | 88 +++++++++
.../impl/TrustMarkOwnersClaimsValidator.java | 81 ++++++++
.../META-INF/net.shibboleth.idp/postconfig.xml | 17 ++
.../EntityConfigurationMetadataCacheTest.java | 219 +++++++++++++++++++++
.../SubordinateStatementMetadataCacheTest.java | 213 ++++++++++++++++++++
19 files changed, 964 insertions(+), 44 deletions(-)
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/EntityConfigurationPayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/EntityConfigurationPayload.java
index 25cd0a7..2d5db58 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/EntityConfigurationPayload.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/EntityConfigurationPayload.java
@@ -19,6 +19,8 @@ import java.util.Map;
import javax.annotation.Nullable;
+import net.shibboleth.idp.plugin.oidc.op.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
@@ -60,6 +62,6 @@ public interface EntityConfigurationPayload extends EntityStatementPayload {
*
* @return trust mark owners
*/
- @Nullable public Map<String, Map<String, Object>> getTrustMarkOwners();
+ @Nullable public Map<String, TrustMarkOwner> getTrustMarkOwners();
}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/claim/TrustMarkOwner.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/claim/TrustMarkOwner.java
new file mode 100644
index 0000000..114ece6
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/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.idp.plugin.oidc.op.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/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/TrustMarkOwnersCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/TrustMarkOwnersCriterion.java
index a17184d..b63fe28 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/TrustMarkOwnersCriterion.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/TrustMarkOwnersCriterion.java
@@ -19,6 +19,7 @@ import java.util.Objects;
import javax.annotation.Nonnull;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.TrustMarkOwner;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.resolver.Criterion;
@@ -28,14 +29,14 @@ import net.shibboleth.shared.resolver.Criterion;
public class TrustMarkOwnersCriterion implements Criterion {
/** The trust mark owners. */
- @Nonnull private final Map<String, Map<String, Object>> 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, Map<String, Object>> trustMarkOwners) {
+ public TrustMarkOwnersCriterion(@Nonnull final Map<String, TrustMarkOwner> trustMarkOwners) {
owners = Constraint.isNotNull(trustMarkOwners, "Trust Mark owners cannot be null");
}
@@ -45,7 +46,7 @@ public class TrustMarkOwnersCriterion implements Criterion {
* @return the trust mark owners value
*/
@Nonnull
- public Map<String, Map<String, Object>> getValue() {
+ public Map<String, TrustMarkOwner> getValue() {
return owners;
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java
index 2c9ad0b..dabaa7d 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java
@@ -104,7 +104,9 @@ public class DefaultEntityConfigurationFetchingStrategy
}
try {
- final SignedJWT jwt = SignedJWT.parse(EntityUtils.toString(response.getEntity()));
+ 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);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java
index 7535bd7..41069f8 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java
@@ -170,7 +170,9 @@ public class DefaultSubordinateStatementFetchingStrategy
}
try {
- final SignedJWT jwt = SignedJWT.parse(EntityUtils.toString(response.getEntity()));
+ 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);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java
index 424c62c..f4a5aea 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/AbstractFederationPolicyConstraint.java
@@ -24,6 +24,7 @@ import org.slf4j.Logger;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubordinateStatement;
import net.shibboleth.idp.plugin.oidc.op.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;
@@ -42,7 +43,7 @@ public abstract class AbstractFederationPolicyConstraint<T extends Object>
/** {@inheritDoc} */
@Override
public boolean validate(@Nullable final Object constraint, @Nonnull final SubordinateStatement subordinateStatement,
- @Nonnull final List<EntityStatement<?>> trustChain) {
+ @Nonnull @Live final List<EntityStatement<?>> trustChain) {
checkComponentActive();
try {
log.trace("Attempting to parse raw constraint value: {}", constraint);
@@ -72,10 +73,10 @@ public abstract class AbstractFederationPolicyConstraint<T extends Object>
*
* @param constraintData the non-null constraint value
* @param subordinateStatement subordinate statement that contains the constraint
- * @param trustChain trust chain to be evaluated
+ * @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 final List<EntityStatement<?>> trustChain);
+ @Nonnull @Live final List<EntityStatement<?>> trustChain);
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/claim/impl/MetadataImpl.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/claim/impl/MetadataImpl.java
index 74b08d8..fbc3784 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/claim/impl/MetadataImpl.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/claim/impl/MetadataImpl.java
@@ -27,6 +27,8 @@ import com.google.common.base.MoreObjects;
import net.shibboleth.idp.plugin.oidc.op.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.
@@ -67,6 +69,10 @@ public class MetadataImpl implements Metadata {
* @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");
@@ -110,7 +116,7 @@ public class MetadataImpl implements Metadata {
* @param metadata federation entity metadata
*/
public void setFederationEntityMetadata(@Nullable final Map<String,Object> metadata) {
- federationEntityMetadata = metadata;
+ federationEntityMetadata = verifyNoNullValues(metadata, "federation_entity");
}
/**
@@ -128,7 +134,7 @@ public class MetadataImpl implements Metadata {
* @param metadata OIDC RP metadata
*/
public void setOpenidRelyingPartyMetadata(@Nullable final Map<String,Object> metadata) {
- openidRelyingPartyMetadata = metadata;
+ openidRelyingPartyMetadata = verifyNoNullValues(metadata, "openid_relying_party");
}
/**
@@ -146,7 +152,7 @@ public class MetadataImpl implements Metadata {
* @param metadata OIDC OP metadata
*/
public void setOpenidProviderMetadata(@Nullable final Map<String,Object> metadata) {
- openidProviderMetadata = metadata;
+ openidProviderMetadata = verifyNoNullValues(metadata, "openid_provider");
}
/**
@@ -164,7 +170,7 @@ public class MetadataImpl implements Metadata {
* @param metadata OAuth AS metadata
*/
public void setOauthAuthorizationServerMetadata(@Nullable final Map<String,Object> metadata) {
- oauthAuthorizationServerMetadata = metadata;
+ oauthAuthorizationServerMetadata = verifyNoNullValues(metadata, "oauth_authorization_server");
}
/**
@@ -182,7 +188,7 @@ public class MetadataImpl implements Metadata {
* @param metadata OAuth client metadata
*/
public void setOauthClientMetadata(@Nullable final Map<String,Object> metadata) {
- oauthClientMetadata = metadata;
+ oauthClientMetadata = verifyNoNullValues(metadata, "oauth_client");
}
/**
@@ -200,7 +206,7 @@ public class MetadataImpl implements Metadata {
* @param metadata OAuth protected resource metadata
*/
public void setOauthResourceMetadata(@Nullable final Map<String,Object> metadata) {
- oauthClientMetadata = metadata;
+ oauthClientMetadata = verifyNoNullValues(metadata, "oauth_resource");
}
/**
@@ -221,7 +227,7 @@ public class MetadataImpl implements Metadata {
*/
@JsonAnySetter
public void setCustomClaims(final String name, final Map<String,Object> value) {
- customClaims.put(name, value);
+ customClaims.put(name, Constraint.isNotNull(value, "Metadata entity type " + name + " cannot be null"));
}
/**
@@ -252,7 +258,26 @@ public class MetadataImpl implements Metadata {
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)
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/claim/impl/TrustMarkOwnerImpl.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/claim/impl/TrustMarkOwnerImpl.java
new file mode 100644
index 0000000..c6b7644
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/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.idp.plugin.oidc.op.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.idp.plugin.oidc.op.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/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/impl/EntityConfigurationPayloadImpl.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/impl/EntityConfigurationPayloadImpl.java
index a752753..f22dddc 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/impl/EntityConfigurationPayloadImpl.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/impl/EntityConfigurationPayloadImpl.java
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.EntityConfigurationPayload;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.TrustMarkOwner;
/**
* Entity Configuration payload claims as defined by the OpenID Federation 1.0 Section 3.2. This class extends the
@@ -47,7 +48,7 @@ public class EntityConfigurationPayloadImpl extends EntityStatementPayloadImpl i
@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, Map<String, Object>> trustMarkOwners;
+ @JsonProperty("trust_mark_owners") private Map<String, TrustMarkOwner> trustMarkOwners;
/**
* Constructor.
@@ -169,7 +170,7 @@ public class EntityConfigurationPayloadImpl extends EntityStatementPayloadImpl i
*
* @return trust mark owners
*/
- @Nullable public Map<String, Map<String, Object>> getTrustMarkOwners() {
+ @Nullable public Map<String, TrustMarkOwner> getTrustMarkOwners() {
return trustMarkOwners;
}
@@ -178,7 +179,7 @@ public class EntityConfigurationPayloadImpl extends EntityStatementPayloadImpl i
*
* @param owners trust mark owners
*/
- public void setTrustMarkOwners(@Nullable Map<String, Map<String, Object>> owners) {
+ public void setTrustMarkOwners(@Nullable Map<String, TrustMarkOwner> owners) {
trustMarkOwners = owners;
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
index d96ac06..94aee56 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
@@ -309,7 +309,12 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
final MetadataPolicy policy = mergedPolicies.get(claim);
try {
final Object enforcedValue = enforceValue(claim, requestMetadata.get(claim), policy);
- requestMetadata.put(claim, enforcedValue);
+ if (enforcedValue != null) {
+ requestMetadata.put(claim, enforcedValue);
+ } else {
+ log.debug("{} Enforcer returned null for entity {} claim {}", getLogPrefix(), entityType,
+ claim);
+ }
} catch (final ConstraintViolationException e) {
log.warn("{} The requested metadata is not compliant with the policy", getLogPrefix());
return OidFederationEventIds.INVALID_METADATA_AGAINST_POLICY;
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
index 30bb795..9b2feff 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
@@ -44,6 +44,7 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityIDCr
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityStatementCriterion;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.TrustMarkOwnersCriterion;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustchain.TrustChainsContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.TrustMarkOwner;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.VerifiedTrustChain;
import net.shibboleth.idp.profile.AbstractProfileAction;
@@ -86,7 +87,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
/** Strategy used to lookup trusted trust mark owners for the trust chain. */
@NonnullAfterInit
- private Function<List<EntityStatement<?>>, Map<String, Map<String, Object>>> trustedTrustMarkOwnersLookupStrategy;
+ private Function<List<EntityStatement<?>>, Map<String, TrustMarkOwner>> trustedTrustMarkOwnersLookupStrategy;
/** Condition to solely take trusted trust mark issuers into account. */
@Nonnull private Predicate<ProfileRequestContext> trustedTrustMarkIssuersOnlyCondition;
@@ -173,7 +174,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
* @param strategy lookup strategy
*/
public void setTrustedTrustMarkOwnersLookupStrategy(
- @Nonnull final Function<List<EntityStatement<?>>, Map<String, Map<String, Object>>> strategy) {
+ @Nonnull final Function<List<EntityStatement<?>>, Map<String, TrustMarkOwner>> strategy) {
checkSetterPreconditions();
trustedTrustMarkOwnersLookupStrategy =
Constraint.isNotNull(strategy, "trustedTrustMarkOwnersLookupStrategy cannot be null");
@@ -343,7 +344,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
log.debug("{} Trusted trust mark issuers {}", getLogPrefix(), trustedIssuers);
assert trustedIssuers != null;
- final Map<String, Map<String, Object>> trustedOwners =
+ final Map<String, TrustMarkOwner> trustedOwners =
Optional.ofNullable(trustedTrustMarkOwnersLookupStrategy.apply(selectedTrustChain))
.orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()));
log.debug("{} Trusted trust mark owners {}", getLogPrefix(), trustedOwners);
@@ -407,7 +408,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
*/
protected boolean checkTrustedIssuer(@Nullable final SignedJWT jwt,
@Nonnull final Map<String, List<String>> trustedIssuers,
- @Nonnull final Map<String, Map<String, Object>> trustedOwners) {
+ @Nonnull final Map<String, TrustMarkOwner> trustedOwners) {
if (jwt == null) {
return false;
}
@@ -452,7 +453,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
* @return true if trust mark verification was successful, false otherwise
*/
protected boolean verifyTrustMark(@Nullable final SignedJWT jwt,
- @Nonnull final Map<String, Map<String, Object>> trustedOwners,
+ @Nonnull final Map<String, TrustMarkOwner> trustedOwners,
@Nonnull final ProfileRequestContext profileRequestContext) {
if (jwt == null) {
return false;
@@ -511,7 +512,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
*/
protected boolean validateDelegatedTrustMark(@Nonnull final JWTClaimsSet trustMarkClaims,
@Nonnull final String id,
- @Nonnull final Map<String, Map<String, Object>> trustedOwners,
+ @Nonnull final Map<String, TrustMarkOwner> trustedOwners,
@Nonnull final ProfileRequestContext profileRequestContext) {
log.debug("{} Validating delegated trust mark {}", getLogPrefix(), id);
try {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
index 91c3c2f..e5aefc3 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
@@ -26,6 +26,7 @@ import org.slf4j.Logger;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.EntityConfigurationPayload;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.TrustMarkOwner;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
@@ -35,7 +36,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
*/
@ThreadSafe
public class DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy
- implements Function<List<EntityStatement<?>>, Map<String, Map<String, Object>>> {
+ implements Function<List<EntityStatement<?>>, Map<String, TrustMarkOwner>> {
/** Class logger. */
@Nonnull private final Logger log =
@@ -43,11 +44,11 @@ public class DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy
/** {@inheritDoc} */
@Nullable @Override
- public Map<String, Map<String, Object>> apply(@Nullable final List<EntityStatement<?>> trustChain) {
+ public Map<String, TrustMarkOwner> apply(@Nullable final List<EntityStatement<?>> trustChain) {
if (trustChain == null || trustChain.size() < 3) {
return null;
}
- final Map<String, Map<String, Object>> ownersClaim =
+ final Map<String, TrustMarkOwner> ownersClaim =
trustChain.get(trustChain.size() - 1).getParsedPayload() instanceof EntityConfigurationPayload ecp ?
ecp.getTrustMarkOwners() : null;
log.debug("Parsed trust_mark_owners claim {}", ownersClaim);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultTrustMarkOwnerCredentialResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultTrustMarkOwnerCredentialResolver.java
index d2176be..763ba7c 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultTrustMarkOwnerCredentialResolver.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultTrustMarkOwnerCredentialResolver.java
@@ -14,11 +14,9 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -31,6 +29,7 @@ import com.nimbusds.jose.jwk.JWKSet;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityIDCriterion;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.TrustMarkOwnersCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.TrustMarkOwner;
import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
@@ -65,21 +64,14 @@ public class DefaultTrustMarkOwnerCredentialResolver extends BasicJOSEObjectCred
"Credential criteria set did not contain an instance of SubjectEntityIDCriterion");
}
final String entityId = subjectCriterion.getValue();
- final Map<String, Map<String, Object>> owners = ownersCriterion.getValue();
+ final Map<String, TrustMarkOwner> owners = ownersCriterion.getValue();
if (owners.isEmpty() || owners.get(entityId) == null) {
log.debug("No trusted owners entry found for {}", entityId);
return CollectionSupport.emptyList();
}
- final Map<String, Object> ownerConfiguration = owners.get(entityId);
- if (ownerConfiguration.get("jwks") instanceof Map<?,?> map) {
- final JWKSet jwkSet;
- try {
- jwkSet = JWKSet.parse(map.entrySet().stream()
- .collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue())));
- } catch (final ParseException e) {
- log.debug("Could not parse JWKSet from the jwks claim", e);
- return CollectionSupport.emptyList();
- }
+ final TrustMarkOwner ownerConfiguration = owners.get(entityId);
+ final JWKSet jwkSet = ownerConfiguration.getJwks();
+ if (jwkSet != null && !jwkSet.isEmpty()) {
final List<Credential> credentials = new ArrayList<>();
for (final JWK jwk : jwkSet.getKeys()) {
if (jwk != null) {
@@ -92,7 +84,7 @@ public class DefaultTrustMarkOwnerCredentialResolver extends BasicJOSEObjectCred
log.debug("Returning credentials {} for {}", credentials, entityId);
return credentials;
}
- log.debug("Could not parse jwks from {}", ownerConfiguration.get("jwks"));
+ log.debug("Could not find jwks from {}", ownerConfiguration);
return CollectionSupport.emptyList();
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/ConstraintsSyntaxClaimsValidator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/ConstraintsSyntaxClaimsValidator.java
new file mode 100644
index 0000000..3e225bb
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/ConstraintsSyntaxClaimsValidator.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+
+/**
+ * A {@link ClaimsValidator} for validating the syntax for standard constraints.
+ */
+public class ConstraintsSyntaxClaimsValidator extends AbstractClaimsValidator {
+
+ /** Generic prefix to be used with the {@link JWTValidationException}. */
+ public static final String ERROR_PREFIX = "Unexpected contents for constraints: ";
+
+ /** {@inheritDoc} */
+ protected void doValidate(@Nonnull final JWTClaimsSet claims,
+ @Nonnull final ProfileRequestContext context) throws JWTValidationException {
+ try {
+ final Map<String, Object> constraints = claims.getJSONObjectClaim("constraints");
+ if (constraints == null || constraints.isEmpty()) {
+ return;
+ }
+ if (constraints.containsKey("max_path_length")) {
+ if (!(constraints.get("max_path_length") instanceof Number)) {
+ throw new JWTValidationException(ERROR_PREFIX + "max_path_length is not a number");
+ }
+ }
+ if (constraints.containsKey("naming_constraints")) {
+ verifyNamingConstraints(constraints.get("naming_constraints"));
+ }
+ if (constraints.containsKey("allowed_entity_types")) {
+ if (constraints.get("allowed_entity_types") instanceof List list) {
+ for (final Object item : list) {
+ if (!(item instanceof String)) {
+ throw new JWTValidationException(ERROR_PREFIX
+ + "allowed_entity_types value is not a list of strings");
+ }
+ }
+ } else {
+ throw new JWTValidationException("allowed_entity_types is not a list");
+ }
+ }
+ } catch (final ParseException e) {
+ throw new JWTValidationException(ERROR_PREFIX + "could not parse a map", e);
+ }
+ }
+
+ /**
+ * Verifies the 'naming_constraints' syntax.
+ *
+ * @param value the naming_constraints value
+ * @throws JWTValidationException if the value syntax is invalid
+ */
+ protected void verifyNamingConstraints(@Nullable final Object value) throws JWTValidationException {
+ final String errorPrefix = ERROR_PREFIX + "naming_constraints ";
+ if (value instanceof Map<?,?> map) {
+ for (final Object key : map.keySet()) {
+ if (!(key instanceof String)) {
+ throw new JWTValidationException(errorPrefix + "key is not a string");
+ }
+ if (map.get(key) instanceof List<?> list) {
+ for (final Object item : list) {
+ if (!(item instanceof String)) {
+ throw new JWTValidationException(errorPrefix + "value is not a list of strings");
+ }
+ }
+ } else {
+ throw new JWTValidationException(errorPrefix + "value is not a list");
+ }
+ }
+ } else {
+ throw new JWTValidationException(errorPrefix + "is not a map");
+ }
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/NonEmptyStringArrayClaimsValidator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/NonEmptyStringArrayClaimsValidator.java
new file mode 100644
index 0000000..ea100a8
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/NonEmptyStringArrayClaimsValidator.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.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl;
+
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A {@link ClaimsValidator} for validating that the configured claims are non-empty string arrays if present.
+ */
+ at ThreadSafeAfterInit
+public class NonEmptyStringArrayClaimsValidator extends AbstractClaimsValidator {
+
+ /** The names of the JWT claims that must be non-empty string arrays if they exist, empty set if none. */
+ @Nonnull @NonnullElements private Set<String> nonEmptyArrayClaims;
+
+ /**
+ * Constructor.
+ */
+ public NonEmptyStringArrayClaimsValidator() {
+ nonEmptyArrayClaims = CollectionSupport.emptySet();
+ }
+
+ /**
+ * Set the non-empty string array claims.
+ *
+ * @param claims the non-empty string array claims.
+ */
+ public void setNonEmptyArrayClaims(@Nullable final Collection<String> claims) {
+ ifInitializedThrowUnmodifiabledComponentException();
+
+ if (claims !=null) {
+ nonEmptyArrayClaims = CollectionSupport.copyToSet(StringSupport.normalizeStringCollection(claims));
+ } else {
+ nonEmptyArrayClaims = CollectionSupport.emptySet();
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected void doValidate(@Nonnull final JWTClaimsSet claims,
+ @Nonnull final ProfileRequestContext context) throws JWTValidationException {
+
+ for (final String claim : nonEmptyArrayClaims) {
+ if (claims.getClaims().containsKey(claim)) {
+ final List<String> values;
+ try {
+ values = claims.getStringListClaim(claim);
+ } catch (final ParseException e) {
+ throw new JWTValidationException("Could not parse " + claim + " into a list of strings");
+ }
+ if (values != null) {
+ if (values.isEmpty()) {
+ throw new JWTValidationException("Empty array is not allowed for " + claim);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/TrustMarkOwnersClaimsValidator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/TrustMarkOwnersClaimsValidator.java
new file mode 100644
index 0000000..1b067fc
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/jwt/claims/impl/TrustMarkOwnersClaimsValidator.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.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl;
+
+import java.text.ParseException;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+
+/**
+ * A {@link ClaimsValidator} for validating the syntax of the optional trust_mark_owners claim. The value must be a
+ * String-keyed map of maps with mandatory sub and jwks claims.
+ */
+ at ThreadSafeAfterInit
+public class TrustMarkOwnersClaimsValidator extends AbstractClaimsValidator {
+
+ /** {@inheritDoc} */
+ protected void doValidate(@Nonnull final JWTClaimsSet claims,
+ @Nonnull final ProfileRequestContext context) throws JWTValidationException {
+ try {
+ final Map<String,Object> trustMarkOwners = claims.getJSONObjectClaim("trust_mark_owners");
+ if (trustMarkOwners != null) {
+ for (final Object key : trustMarkOwners.keySet()) {
+ if (key instanceof String string) {
+ final Object raw = trustMarkOwners.get(string);
+ if (raw instanceof Map<?, ?> map) {
+ final Map<String, Object> trustMarkOwner = map.keySet().stream()
+ .filter(String.class::isInstance)
+ .map(String.class::cast)
+ .filter(k -> map.get(k) != null)
+ .collect(Collectors.toMap(k -> k, k -> map.get(k)));
+ if (trustMarkOwner.get("sub") instanceof String subString) {
+ if (subString.isEmpty()) {
+ throw new JWTValidationException("Unexpected contents for trust_mark_owners item: "
+ + "Subject is empty");
+ }
+ } else {
+ throw new JWTValidationException("Unexpected contents for trust_mark_owners item: "
+ + "Subject is not a string");
+ }
+ if (trustMarkOwner.get("jwks") == null) {
+ throw new JWTValidationException("Unexpected contents for trust_mark_owners item: "
+ + "Value for 'jwks' is missing");
+ }
+ } else {
+ throw new JWTValidationException("Unexpected contents for trust_mark_owners item: "
+ + "Value is not a map");
+ }
+ } else {
+ throw new JWTValidationException("Unexpected contents for trust_mark_owners item: "
+ + "Key is not a string");
+ }
+ }
+ }
+ } catch (final ParseException e) {
+ throw new JWTValidationException("Unexpected contents for trust_mark_owners: could not parse a map", e);
+ }
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 74722fa..a64e8d5 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -172,6 +172,9 @@
<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>
@@ -309,6 +312,7 @@
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>
@@ -741,6 +745,17 @@
</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" />
@@ -1103,6 +1118,8 @@
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>
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
index f1e1cce..3aa17fd 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
@@ -23,6 +23,7 @@ import java.net.URISyntaxException;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -31,9 +32,18 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEObject;
+import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
import net.shibboleth.idp.plugin.oidc.op.oidfed.TrustChainTestUtil;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
@@ -45,6 +55,7 @@ import net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed.EntityConfiguration
import net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed.support.CustomEntityConfigurationFilterStrategy;
import net.shibboleth.oidc.metadata.cache.MetadataCache;
import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.resolver.CriteriaSet;
/**
@@ -121,6 +132,47 @@ public class EntityConfigurationMetadataCacheTest extends AbstractFederationFlow
}
}
+ @Test
+ public void testUnsignedEntityConfiguration()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String entityConfiguration = new PlainJWT(builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testEncryptedEntityConfiguration()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final SignedJWT signedJwt = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build());
+ final JWEObject jwe = new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .build(),
+ new Payload(signedJwt));
+ try {
+ jwe.encrypt(new RSAEncrypter(leafKey.toRSAKey()));
+ } catch (JOSEException e) {
+ Assert.fail("Encryption failed", e);
+ }
+ final String entityConfiguration = jwe.serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
@Test
public void testWithUnrecognizedCriticalClaim()
throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
@@ -299,6 +351,77 @@ public class EntityConfigurationMetadataCacheTest extends AbstractFederationFlow
assertNoEntityConfiguration(entityId);
}
+ @Test
+ public void testEmptyAuthorityHintsClaim()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("authority_hints", CollectionSupport.emptyList())
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testEmptyTrustAnchorHintsClaim()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_anchor_hints", CollectionSupport.emptyList())
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testNullFederationEntityMetadata()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final Map<String, Object> metadata = new HashMap<>();
+ metadata.put("federation_entity", null);
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", metadata);
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testNullCustomMetadata()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final Map<String, Object> metadata = new HashMap<>();
+ metadata.put("federation_entity", Collections.emptyMap());
+ metadata.put("custom_type", null);
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", metadata);
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
@Test
public void testNullResponse()
throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
@@ -316,6 +439,102 @@ public class EntityConfigurationMetadataCacheTest extends AbstractFederationFlow
assertNoEntityConfiguration(entityId);
}
+ @Test
+ public void testInvalidtTrustMark_nonMatchingType()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final String trustMark = TrustChainTestUtil.trustMark(JWSAlgorithm.RS256, trustMarkIssuerKey, trustMarkIssuerId,
+ entityId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_marks", List.of(Map.of(
+ "trust_mark_type", "https://example.org/non-matching-type",
+ "trust_mark", trustMark)))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testInvalidtTrustMarkIssuers_invalidFormat()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_mark_issuers", List.of(trustMarkIssuerId))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testInvalidtTrustMarkOwners_invalidJwks()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final Map<String,Object> trustMarkOwner = new HashMap<>();
+ trustMarkOwner.put("sub", trustMarkIssuerId);
+ trustMarkOwner.put("jwks", Collections.emptyMap());
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_mark_owners", Map.of("https://example.org/email-allowing-trust-mark", trustMarkOwner))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testInvalidtTrustMarkOwners_missingJwks()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final Map<String,Object> trustMarkOwner = new HashMap<>();
+ trustMarkOwner.put("sub", trustMarkIssuerId);
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_mark_owners", Map.of("https://example.org/email-allowing-trust-mark", trustMarkOwner))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testInvalidtTrustMarkOwners_missingSub()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final Map<String,Object> trustMarkOwner = new HashMap<>();
+ trustMarkOwner.put("jwks", new JWKSet(leafKey).toJSONObject(true));
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_mark_owners", Map.of("https://example.org/email-allowing-trust-mark", trustMarkOwner))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
protected void assertNoEntityConfiguration(final String entityId) {
try {
final List<EntityConfigurationContainer> result =
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
index cb71ce2..0f5b20a 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
@@ -23,6 +23,7 @@ import java.net.URISyntaxException;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -31,9 +32,18 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
import net.shibboleth.idp.plugin.oidc.op.oidfed.TrustChainTestUtil;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubordinateStatement;
@@ -128,6 +138,51 @@ public class SubordinateStatementMetadataCacheTest extends AbstractFederationFlo
}
}
+ @Test
+ public void testUnsignedSubordinateStatement()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String subordinateStatement = new PlainJWT(builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ assertNoSubordinateStatement(entityId);
+ }
+
+ @Test
+ public void testEncryptedSubordinateStatement()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final SignedJWT signedJwt = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, "entity-statement+jwt", builder.build());
+ final JWEObject jwe = new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .build(),
+ new Payload(signedJwt));
+ try {
+ jwe.encrypt(new RSAEncrypter(leafKey.toRSAKey()));
+ } catch (JOSEException e) {
+ Assert.fail("Encryption failed", e);
+ }
+ final String subordinateStatement = jwe.serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ assertNoSubordinateStatement(entityId);
+ }
+
@Test
public void testWithUnrecognizedCriticalClaim()
throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
@@ -293,6 +348,164 @@ public class SubordinateStatementMetadataCacheTest extends AbstractFederationFlo
assertNoSubordinateStatement(entityId);
}
+ @Test
+ public void testInvalidConstraints_maxPathLength()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("constraints", Map.of("max_path_length", "non_integer"))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ assertNoSubordinateStatement(entityId);
+ }
+
+ @Test
+ public void testInvalidConstraints_namingConstraint()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("constraints", Map.of("naming_constraints", "not_map"))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ assertNoSubordinateStatement(entityId);
+ }
+
+ @Test
+ public void testInvalidConstraints_allowedEntityTypes()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("constraints", Map.of("allowed_entity_types", "not_list"))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ assertNoSubordinateStatement(entityId);
+ }
+
+ @Test
+ public void testValidConstraints()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final Map<String,Object> constraints = new HashMap<>();
+ constraints.put("max_path_length", 5);
+ constraints.put("allowed_entity_types", List.of("openid_relying_party"));
+ constraints.put("naming_constraints", Map.of("permitted", List.of(".example.com")));
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("constraints", constraints)
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ try {
+ final List<SubordinateStatementContainer> result =
+ subordinateStatementCache.get(new CriteriaSet(new SubjectEntityIDCriterion(entityId),
+ new ResponseContainerExpirationCriterion(Instant.now().plusSeconds(300)),
+ new IssuerEntityIDCriterion(anchorId)));
+ Assert.assertNotNull(result);
+ Assert.assertEquals(result.size(), 1);
+ final SubordinateStatement statement = result.get(0).getStatement();
+ Assert.assertNotNull(statement);
+ assert statement != null;
+ Assert.assertNull(statement.getParsedPayload().getCustomClaims()
+ .get(CustomSubordinateStatementFilterStrategy.CUSTOM_CLAIM_NAME));
+ } catch (MetadataCacheException e) {
+ Assert.fail("Could not resolve entity configuration", e);
+ }
+ }
+
+ @Test
+ public void testForbiddenClaim_trustMark()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final String trustMark = TrustChainTestUtil.trustMark(JWSAlgorithm.RS256, trustMarkIssuerKey, trustMarkIssuerId,
+ entityId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_marks", List.of(Map.of(
+ "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+ "trust_mark", trustMark)))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ assertNoSubordinateStatement(entityId);
+ }
+
+ @Test
+ public void testForbiddenClaim_trustMarkIssuers()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_mark_issuers", List.of(trustMarkIssuerId))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ assertNoSubordinateStatement(entityId);
+ }
+
+ @Test
+ public void testForbiddenClaim_trustMarkOwners()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final Map<String,Object> trustMarkOwner = new HashMap<>();
+ trustMarkOwner.put("sub", trustMarkIssuerId);
+ trustMarkOwner.put("jwks", new JWKSet(leafKey).toJSONObject(true));
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("trust_mark_owners", Map.of("https://example.org/email-allowing-trust-mark", trustMarkOwner))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, "entity-statement+jwt", builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+ assertNoSubordinateStatement(entityId);
+ }
+
@Test
public void testInvalidJwksClaim()
throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list