[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Apr 25 13:24:24 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=e8a6e6d4b656cb925d141fef744557ca0c689d17
The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
new e8a6e6d4 JOIDC-222 - Support for OpenID Federation
e8a6e6d4 is described below
commit e8a6e6d4b656cb925d141fef744557ca0c689d17
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Apr 25 16:24:07 2025 +0300
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
Improved trust mark validation
- Now capable of validating delegated trust marks
- Exploit claim validators for iss, sub, iat, exp and trust_mark_id
- May be customized via shibboleth.oidfed.TrustMarkClaimsValidationLookupStrategy and shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy
---
...tChainTrustedTrustMarkOwnersLookupStrategy.java | 103 ++++++++
.../DefaultTrustMarkOwnerCredentialResolver.java | 97 ++++++++
.../oidfed/metadata/TrustMarkOwnersCriterion.java | 80 ++++++
.../op/oidfed/profile/impl/ResolveTrustMarks.java | 268 ++++++++++++++++++---
.../flows/oidc/abstract/oidc-abstract-beans.xml | 23 ++
.../oidc/metadata-lookup/metadata-lookup-beans.xml | 15 +-
.../idp/flows/oidfed/register/register-beans.xml | 15 +-
.../oidfed/resolve-entity/resolve-entity-beans.xml | 15 +-
8 files changed, 573 insertions(+), 43 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
new file mode 100644
index 00000000..d1c7e030
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.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.idp.plugin.oidc.op.oidfed.metadata;
+
+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 com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+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 function for fetching trusted trust mark owners from a trust chain: they are read from the trust anchor's
+ * entity configuration.
+ */
+public class DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy extends AbstractIdentifiableInitializableComponent
+ implements Function<List<EntityStatement>, Map<String, Map<String, Object>>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log =
+ LoggerFactory.getLogger(DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.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} */
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("Object mapper cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable @Override
+ public Map<String, Map<String, Object>> apply(@Nullable final List<EntityStatement> trustChain) {
+ checkComponentActive();
+ if (trustChain == null || trustChain.size() < 3) {
+ return null;
+ }
+ final Object ownersClaim =
+ trustChain.get(trustChain.size() - 1).getClaimsSet().getClaim("trust_mark_owners");
+ log.debug("Raw trust_mark_owners claim {}", ownersClaim);
+ if (ownersClaim != null) {
+ final JavaType objectType = objectMapper.constructType(Object.class);
+ final JavaType stringType = objectMapper.constructType(String.class);
+ final MapType objectMapType =
+ objectMapper.getTypeFactory().constructMapType(Map.class, stringType, objectType);
+ final MapType rootMapType =
+ objectMapper.getTypeFactory().constructMapType(Map.class, stringType, objectMapType);
+ try {
+ final Map<String, Map<String, Object>> result =
+ objectMapper.readValue(ownersClaim.toString(), rootMapType);
+ if (result != null) {
+ log.debug("Parsed trust_mark_owners map {}", result);
+ return result;
+ }
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not parse trust mark issuers from the trust chain", e);
+ }
+ }
+ log.debug("Returning empty map");
+ return CollectionSupport.emptyMap();
+ }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkOwnerCredentialResolver.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkOwnerCredentialResolver.java
new file mode 100644
index 00000000..14c9d9c5
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkOwnerCredentialResolver.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.idp.plugin.oidc.op.oidfed.metadata;
+
+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;
+
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Default resolver for trust anchor owner key resolution. A {@link TrustMarkOwnersCriterion} is used for fetching the
+ * credentials for the trust mark owner fetched via {@link SubjectEntityIDCriterion}.
+ */
+public class DefaultTrustMarkOwnerCredentialResolver extends BasicJOSEObjectCredentialResolver {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultTrustMarkOwnerCredentialResolver.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+ if (criteriaSet == null) {
+ throw new ResolverException("No criteria supplied");
+ }
+ final TrustMarkOwnersCriterion ownersCriterion = criteriaSet.get(TrustMarkOwnersCriterion.class);
+ if (ownersCriterion == null) {
+ log.debug("No TrustMarkOwnersCriterion criteria supplised, resolver could not process");
+ throw new ResolverException(
+ "Credential criteria set did not contain an instance of TrustMarkOwnersCriterion");
+ }
+ final SubjectEntityIDCriterion subjectCriterion = criteriaSet.get(SubjectEntityIDCriterion.class);
+ if (subjectCriterion == null) {
+ log.debug("No SubjectEntityIDCriterion criteria supplied, resolver could not process");
+ throw new ResolverException(
+ "Credential criteria set did not contain an instance of SubjectEntityIDCriterion");
+ }
+ final String entityId = subjectCriterion.getValue();
+ final Map<String, Map<String, Object>> 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 List<Credential> credentials = new ArrayList<>();
+ for (final JWK jwk : jwkSet.getKeys()) {
+ if (jwk != null) {
+ final Credential cred = buildJWKCredential(jwk, null);
+ if (cred != null) {
+ credentials.add(cred);
+ }
+ }
+ }
+ log.debug("Returning credentials {} for {}", credentials, entityId);
+ return credentials;
+ }
+ log.debug("Could not parse jwks from {}", ownerConfiguration.get("jwks"));
+ return CollectionSupport.emptyList();
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkOwnersCriterion.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkOwnersCriterion.java
new file mode 100644
index 00000000..56a19bbb
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkOwnersCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
+
+import java.util.Map;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+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, Map<String, Object>> owners;
+
+ /**
+ * Constructor.
+ *
+ * @param trustMarkOwners the truts mark owners, must not be null
+ */
+ public TrustMarkOwnersCriterion(@Nonnull final Map<String, Map<String, Object>> 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, Map<String, Object>> 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/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
index 34d729fb..0b194d61 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
@@ -44,7 +44,10 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustMarksParsingStrategy;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityStatementCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TrustMarkOwnersCriterion;
import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
import net.shibboleth.oidc.metadata.cache.MetadataCache;
import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -82,6 +85,10 @@ public class ResolveTrustMarks extends AbstractProfileAction {
@NonnullAfterInit
private Function<List<EntityStatement>, Map<String, List<String>>> trustedTrustMarkIssuersLookupStrategy;
+ /** Strategy used to lookup trusted trust mark owners for the trust chain. */
+ @NonnullAfterInit
+ private Function<List<EntityStatement>, Map<String, Map<String, Object>>> trustedTrustMarkOwnersLookupStrategy;
+
/** Condition to solely take trusted trust mark issuers into account. */
@Nonnull private Predicate<ProfileRequestContext> trustedTrustMarkIssuersOnlyCondition;
@@ -91,18 +98,28 @@ public class ResolveTrustMarks extends AbstractProfileAction {
/** Trust engine used to validate a trust mark signature. */
@NonnullAfterInit private TrustEngine<SignedJWT> trustEngine;
+ /** Trust engine used to validate a delegated trust mark signature. */
+ @NonnullAfterInit private TrustEngine<SignedJWT> delegationTrustEngine;
+
+ /** Strategy used to lookup trust mark claims validator. */
+ @NonnullAfterInit private Function<ProfileRequestContext,ClaimsValidator> trustMarkClaimsValidationLookupStrategy;
+
+ /** Strategy used to lookup delegated trust mark claims validator. */
+ @NonnullAfterInit
+ private Function<ProfileRequestContext,ClaimsValidator> delegatedTrustMarkClaimsValidationLookupStrategy;
+
/** Trust chain context to operate on. */
@NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
/** The selected trust chain to resolve trust marks from. */
@NonnullBeforeExec private List<EntityStatement> selectedTrustChain;
- /** Flag indicating that only trusted trust mark issuers are taken into account. */
- private boolean onlyTrustedIssuers;
-
- /** Map of trusted trust mark issuers. */
- @NonnullBeforeExec private Map<String, List<String>> trustedIssuers;
+ /** Trust mark claims validator. */
+ @NonnullBeforeExec private ClaimsValidator trustMarkClaimsValidator;
+ /** Delegated trust mark claims validator. */
+ @NonnullBeforeExec private ClaimsValidator delegatedTrustMarkClaimsValidator;
+
/**
* Constructor.
*/
@@ -140,6 +157,18 @@ public class ResolveTrustMarks extends AbstractProfileAction {
Constraint.isNotNull(strategy, "trustedTrustMarkIssuersLookupStrategy cannot be null");
}
+ /**
+ * Set the strategy used to lookup trusted trust mark issuers for the trust chain.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setTrustedTrustMarkOwnersLookupStrategy(
+ @Nonnull final Function<List<EntityStatement>, Map<String, Map<String, Object>>> strategy) {
+ checkSetterPreconditions();
+ trustedTrustMarkOwnersLookupStrategy =
+ Constraint.isNotNull(strategy, "trustedTrustMarkOwnersLookupStrategy cannot be null");
+ }
+
/**
* Set the condition to solely take trusted trust mark issuers into account.
*
@@ -171,6 +200,40 @@ public class ResolveTrustMarks extends AbstractProfileAction {
trustEngine = Constraint.isNotNull(engine, "Trust Engine cannot be null");
}
+ /**
+ * Set trust engine used to validate a delegated trust mark signature.
+ *
+ * @param engine trust engine
+ */
+ public void setDelegationTrustEngine(@Nonnull final TrustEngine<SignedJWT> engine) {
+ checkSetterPreconditions();
+ delegationTrustEngine = Constraint.isNotNull(engine, "Delegation Trust Engine cannot be null");
+ }
+
+ /**
+ * Set the strategy used to lookup trust mark claims validator.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setTrustMarkClaimsValidationLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, ClaimsValidator> strategy) {
+ checkSetterPreconditions();
+ trustMarkClaimsValidationLookupStrategy =
+ Constraint.isNotNull(strategy, "TrustMarkClaimsValidationLookupStrategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to lookup delegated trust mark claims validator.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setDelegatedTrustMarkClaimsValidationLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, ClaimsValidator> strategy) {
+ checkSetterPreconditions();
+ delegatedTrustMarkClaimsValidationLookupStrategy =
+ Constraint.isNotNull(strategy, "DelegatedTrustMarkClaimsValidationLookupStrategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -182,9 +245,22 @@ public class ResolveTrustMarks extends AbstractProfileAction {
if (trustEngine == null) {
throw new ComponentInitializationException("Trust Engine cannot be null");
}
+ if (delegationTrustEngine == null) {
+ throw new ComponentInitializationException("Delegation Trust Engine cannot be null");
+ }
if (trustedTrustMarkIssuersLookupStrategy == null) {
throw new ComponentInitializationException("Trusted trust mark issuers lookup strategy cannot be null");
}
+ if (trustedTrustMarkOwnersLookupStrategy == null) {
+ throw new ComponentInitializationException("Trusted trust mark owners lookup strategy cannot be null");
+ }
+ if (trustMarkClaimsValidationLookupStrategy == null) {
+ throw new ComponentInitializationException("TrustMarkClaimsValidationLookupStrategy cannot be null");
+ }
+ if (delegatedTrustMarkClaimsValidationLookupStrategy == null) {
+ throw new ComponentInitializationException(
+ "DelegatedTrustMarkClaimsValidationLookupStrategy cannot be null");
+ }
}
/** {@inheritDoc} */
@@ -219,31 +295,55 @@ public class ResolveTrustMarks extends AbstractProfileAction {
return false;
}
- onlyTrustedIssuers = trustedTrustMarkIssuersOnlyCondition.test(profileRequestContext);
- trustedIssuers = Optional.ofNullable(trustedTrustMarkIssuersLookupStrategy.apply(selectedTrustChain))
- .orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()));
+ trustMarkClaimsValidator = trustMarkClaimsValidationLookupStrategy.apply(profileRequestContext);
+ if (trustMarkClaimsValidator == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ log.error("{} Unable to locate trust mark claims validator", getLogPrefix());
+ return false;
+ }
- if (trustedIssuers.isEmpty()) {
- if (onlyTrustedIssuers) {
- log.debug("{} No trusted issuers set in the selected trust anchor, trust marks won't be resolved",
- getLogPrefix());
- return false;
- }
+ delegatedTrustMarkClaimsValidator =
+ delegatedTrustMarkClaimsValidationLookupStrategy.apply(profileRequestContext);
+ if (delegatedTrustMarkClaimsValidator == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ log.error("{} Unable to locate delegated trust mark claims validator", getLogPrefix());
+ return false;
}
+
return true;
}
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- log.debug("{} Trusted trust mark issuers {}", getLogPrefix(), trustedIssuers);
+ final boolean onlyTrustedIssuers = trustedTrustMarkIssuersOnlyCondition.test(profileRequestContext);
+
final Map<String, List<SignedJWT>> chainTrustMarks =
- trustChainTrustMarksParsingStrategy.apply(selectedTrustChain);
+ Optional.ofNullable(trustChainTrustMarksParsingStrategy.apply(selectedTrustChain))
+ .orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()))
+ .entrySet().stream()
+ .collect(Collectors.toMap(entry -> entry.getKey(),
+ entry -> entry.getValue().stream()
+ .filter(trustMark ->
+ validateClaims(trustMarkClaimsValidator, trustMark, profileRequestContext))
+ .toList()));
if (chainTrustMarks == null || chainTrustMarks.isEmpty()) {
- log.debug("{} No trust marks found from the selected trust chain", getLogPrefix());
+ log.debug("{} No valid trust marks found from the selected trust chain", getLogPrefix());
return;
}
+ final Map<String, List<String>> trustedIssuers =
+ Optional.ofNullable(trustedTrustMarkIssuersLookupStrategy.apply(selectedTrustChain))
+ .orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()));
+ log.debug("{} Trusted trust mark issuers {}", getLogPrefix(), trustedIssuers);
+ assert trustedIssuers != null;
+
+ final Map<String, Map<String, Object>> trustedOwners =
+ Optional.ofNullable(trustedTrustMarkOwnersLookupStrategy.apply(selectedTrustChain))
+ .orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()));
+ log.debug("{} Trusted trust mark owners {}", getLogPrefix(), trustedOwners);
+ assert trustedOwners != null;
+
final Map<String, List<SignedJWT>> verifiedTrustMarks = new HashMap<>();
for (final EntityStatement statement : selectedTrustChain) {
final List<SignedJWT> trustMarks = chainTrustMarks.get(statement.getEntityID().getValue());
@@ -253,8 +353,9 @@ public class ResolveTrustMarks extends AbstractProfileAction {
verifiedTrustMarks.put(
statement.getEntityID().getValue(),
trustMarks.stream()
- .filter(entry -> checkTrustedIssuer(entry))
- .filter(entry -> verifyTrustMark(entry))
+ .filter(entry -> onlyTrustedIssuers ?
+ checkTrustedIssuer(entry, trustedIssuers, trustedOwners) : true)
+ .filter(entry -> verifyTrustMark(entry, trustedOwners, profileRequestContext))
.filter(Objects::nonNull)
.toList());
}
@@ -267,36 +368,71 @@ public class ResolveTrustMarks extends AbstractProfileAction {
trustChainContext.setVerifiedTrustMarkIds(verifiedTrustMarkIds);
}
+ /**
+ * Validates the given trust mark JWT against the given claims validator.
+ *
+ * @param claimsValidator the claims validator (chain)
+ * @param jwt the trust mark
+ * @param profileRequestContext the profile request context
+ * @return true if validation succeeded, false otherwise
+ */
+ protected boolean validateClaims(@Nullable final ClaimsValidator claimsValidator, @Nullable final SignedJWT jwt,
+ @Nonnull final ProfileRequestContext profileRequestContext) {
+ if (claimsValidator == null || jwt == null) {
+ return false;
+ }
+ try {
+ final JWTClaimsSet claimsSet = jwt.getJWTClaimsSet();
+ assert claimsSet != null;
+ claimsValidator.validate(claimsSet, profileRequestContext);
+ return true;
+ } catch (final JWTValidationException | ParseException e) {
+ log.debug("{} Claims validation failed", getLogPrefix(), e);
+ }
+ return false;
+ }
+
/**
* Verifies the given trust mark meets configuration for trusted trust mark issuers.
*
* @param jwt the trust mark to be verified
+ * @param trustedIssuers the trusted trust mark issuers
+ * @param trustedOwners the trusted trust mark owners
* @return true if the trust mark meets configuration, false otherwise
*/
- protected boolean checkTrustedIssuer(@Nullable final SignedJWT jwt) {
+ protected boolean checkTrustedIssuer(@Nullable final SignedJWT jwt,
+ @Nonnull final Map<String, List<String>> trustedIssuers,
+ @Nonnull final Map<String, Map<String, Object>> trustedOwners) {
if (jwt == null) {
return false;
}
- if (onlyTrustedIssuers) {
- try {
- final JWTClaimsSet claimsSet = jwt.getJWTClaimsSet();
- final String id = StringSupport.trimOrNull(getTrustMarkId(jwt));
- if (id == null) {
+ try {
+ final JWTClaimsSet claimsSet = jwt.getJWTClaimsSet();
+ final String id = StringSupport.trimOrNull(getTrustMarkId(jwt));
+ if (id == null) {
+ return false;
+ }
+ if (trustedIssuers.containsKey(id)) {
+ final String issuer = claimsSet.getIssuer();
+ assert issuer != null;
+ final List<String> validIssuers = trustedIssuers.get(id);
+ if (validIssuers == null || !validIssuers.contains(issuer)) {
+ log.debug("{} Issuer {} is not valid trust mark issuer", getLogPrefix(), issuer);
return false;
}
- if (trustedIssuers.containsKey(id)) {
- final String issuer = claimsSet.getIssuer();
- assert issuer != null;
- final List<String> validIssuers = trustedIssuers.get(id);
- if (onlyTrustedIssuers && (validIssuers == null || !validIssuers.contains(issuer)) ) {
- log.debug("{} Issuer {} is not valid trust mark issuer", getLogPrefix(), issuer);
- return false;
- }
+ } else if (trustedOwners.containsKey(id)) {
+ log.debug("{} Trust mark ID {} is included in trusted owners", getLogPrefix(), id);
+ if (claimsSet.getStringClaim("delegation") == null) {
+ log.debug("(} Trust mark ID {} does not contain a delegation claim", getLogPrefix(), id);
+ return false;
}
- } catch (final ParseException e) {
- log.error("Could not parse TrustMark JWT contents", e);
+ } else {
+ log.debug("{} Trust mark ID {} is not included in the trusted issuers", getLogPrefix(), id);
return false;
}
+ } catch (final ParseException e) {
+ log.error("{} Could not parse TrustMark JWT contents", getLogPrefix(), e);
+ return false;
}
return true;
}
@@ -306,9 +442,12 @@ public class ResolveTrustMarks extends AbstractProfileAction {
* entity configuration and (2) the trust engine for validating the trust mark signature.
*
* @param jwt the trust mark to be verified
+ * @param profileRequestContext the profile request context
* @return true if trust mark verification was successful, false otherwise
*/
- protected boolean verifyTrustMark(@Nullable final SignedJWT jwt) {
+ protected boolean verifyTrustMark(@Nullable final SignedJWT jwt,
+ @Nonnull final Map<String, Map<String, Object>> trustedOwners,
+ @Nonnull final ProfileRequestContext profileRequestContext) {
if (jwt == null) {
return false;
}
@@ -321,7 +460,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
}
final String issuer = trustMarkClaims.getIssuer();
assert issuer != null;
- log.debug("Resolving trust chain for {}", issuer);
+ log.debug("{} Resolving trust chain for {}", getLogPrefix(), issuer);
final List<List<List<EntityStatement>>> cacheResult;
try {
cacheResult = trustChainCache.get(new CriteriaSet(new SubjectEntityIDCriterion(issuer)));
@@ -339,11 +478,60 @@ public class ResolveTrustMarks extends AbstractProfileAction {
final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(trustMarkIssuer));
try {
if (trustEngine.validate(jwt, criteria)) {
- log.debug("Successfully validated trust mark issued by {}", issuer);
- return true;
+ final String id = getTrustMarkId(jwt);
+ assert id != null;
+ log.debug("{} Successfully validated trust mark {} issued by {}", getLogPrefix(), id, issuer);
+ if (trustedOwners.containsKey(id)) {
+ return validateDelegatedTrustMark(trustMarkClaims, id, trustedOwners, profileRequestContext);
+ } else {
+ return true;
+ }
}
} catch (final SecurityException e) {
- log.debug("Security exception while validating trust mark signature for {}", issuer, e);
+ log.debug("{} Security exception while validating trust mark signature for {}", getLogPrefix(), issuer, e);
+ }
+ return false;
+ }
+
+ /**
+ * Validates a delegated trust mark.
+ *
+ * @param trustMarkClaims the claims set containing delegation claim
+ * @param id the trust mark identifier
+ * @param trustedOwners the trusted trust mark owners
+ * @param profileRequestContext the profile request context
+ * @return true if delegation JWT was valid, false otherwise
+ */
+ protected boolean validateDelegatedTrustMark(@Nonnull final JWTClaimsSet trustMarkClaims,
+ @Nonnull final String id,
+ @Nonnull final Map<String, Map<String, Object>> trustedOwners,
+ @Nonnull final ProfileRequestContext profileRequestContext) {
+ log.debug("{} Validating delegated trust mark {}", getLogPrefix(), id);
+ try {
+ final SignedJWT delegationJwt = SignedJWT.parse(trustMarkClaims.getStringClaim("delegation"));
+ final CriteriaSet delegationCriteria = new CriteriaSet(
+ new TrustMarkOwnersCriterion(trustedOwners),
+ new SubjectEntityIDCriterion(id));
+ if (validateClaims(delegatedTrustMarkClaimsValidator, delegationJwt, profileRequestContext)) {
+ assert delegationJwt != null;
+ if (delegationTrustEngine.validate(delegationJwt, delegationCriteria)) {
+ final String issuer = delegationJwt.getJWTClaimsSet().getIssuer();
+ log.debug("{} Successfully validated delegated {} signature issued by {}", getLogPrefix(), id,
+ issuer);
+ if (issuer != null && issuer.equals(trustMarkClaims.getSubject())) {
+ return true;
+ } else {
+ log.debug("{} The issuer of the delegation {} does not match with the subject {}",
+ getLogPrefix(), issuer, trustMarkClaims.getSubject());
+ }
+ }
+ }
+ } catch (final SecurityException e) {
+ log.debug("{} Security exception while validating trust mark signature for {}", getLogPrefix(),
+ trustMarkClaims.getIssuer(), e);
+ } catch (final ParseException e) {
+ log.debug("{} Parsing exception while processing delegated trust mark from {}", getLogPrefix(),
+ trustMarkClaims.getIssuer(), e);
}
return false;
}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
index 79b9ba1c..2b6a33a3 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
@@ -146,4 +146,27 @@
p:activationCondition-ref="shibboleth.Conditions.FALSE">
</bean>
+ <bean id="DefaultTrustMarkClaimsValidationLookupStrategy" 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_id" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ </util:list>
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+
</beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
index 80935f1b..29e7166a 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
@@ -138,7 +138,9 @@
<bean id="ResolveTrustMarks" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustMarks"
scope="prototype"
- p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}">
+ p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+ p:trustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.TrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}"
+ p:delegatedTrustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}">
<property name="trustEngine">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TokenPayloadAsymmetricKeyTrustEngine">
<constructor-arg>
@@ -146,10 +148,21 @@
</constructor-arg>
</bean>
</property>
+ <property name="delegationTrustEngine">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TokenPayloadAsymmetricKeyTrustEngine">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustMarkOwnerCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
<property name="trustedTrustMarkIssuersLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy"
p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
</property>
+ <property name="trustedTrustMarkOwnersLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+ </property>
</bean>
<bean id="ValidateAutomaticRegistrationProfileConfiguration"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index cc1e301a..3cb3d50c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -112,7 +112,9 @@
<bean id="ResolveTrustMarks" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustMarks"
scope="prototype"
- p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}">
+ p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+ p:trustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.TrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}"
+ p:delegatedTrustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}">
<property name="trustEngine">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TokenPayloadAsymmetricKeyTrustEngine">
<constructor-arg>
@@ -120,10 +122,21 @@
</constructor-arg>
</bean>
</property>
+ <property name="delegationTrustEngine">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TokenPayloadAsymmetricKeyTrustEngine">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustMarkOwnerCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
<property name="trustedTrustMarkIssuersLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy"
p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
</property>
+ <property name="trustedTrustMarkOwnersLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+ </property>
</bean>
<bean id="RelyingPartyTrustChainContextLookupStrategy" parent="shibboleth.Functions.Expression"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
index 57377fad..ae78b670 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
@@ -109,7 +109,9 @@
<bean id="ResolveTrustMarks" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustMarks"
scope="prototype"
- p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}">
+ p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+ p:trustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.TrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}"
+ p:delegatedTrustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}">
<property name="trustEngine">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TokenPayloadAsymmetricKeyTrustEngine">
<constructor-arg>
@@ -117,10 +119,21 @@
</constructor-arg>
</bean>
</property>
+ <property name="delegationTrustEngine">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TokenPayloadAsymmetricKeyTrustEngine">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustMarkOwnerCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
<property name="trustedTrustMarkIssuersLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy"
p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
</property>
+ <property name="trustedTrustMarkOwnersLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+ </property>
</bean>
<bean id="PopulateEntityStatementSignatureSigningParameters"
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list