com.nimbusds.openid.connect.sdk.federation.trust.DefaultTrustChainRetriever Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oauth2-oidc-sdk Show documentation
Show all versions of oauth2-oidc-sdk Show documentation
OAuth 2.0 SDK with OpenID Connection extensions for developing client
and server applications.
/*
* oauth2-oidc-sdk
*
* Copyright 2012-2020, Connect2id Ltd and contributors.
*
* 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 com.nimbusds.openid.connect.sdk.federation.trust;
import java.net.URI;
import java.util.*;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.util.CollectionUtils;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityID;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
import com.nimbusds.openid.connect.sdk.federation.entities.FederationEntityMetadata;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityType;
import com.nimbusds.openid.connect.sdk.federation.trust.constraints.TrustChainConstraints;
/**
* The default trust chain retriever.
*/
class DefaultTrustChainRetriever implements TrustChainRetriever {
private final EntityStatementRetriever retriever;
private final TrustChainConstraints constraints;
private final List accumulatedExceptions = new LinkedList<>();
private final Map accumulatedTrustAnchorJWKSets = new HashMap<>();
/**
* Creates a new trust chain retriever, with
* {@link TrustChainConstraints#NO_CONSTRAINTS no trust chain
* constraints}.
*
* @param retriever The entity statement retriever. Must not be
* {@code null}.
*/
DefaultTrustChainRetriever(final EntityStatementRetriever retriever) {
this(retriever, TrustChainConstraints.NO_CONSTRAINTS);
}
/**
* Creates a new trust chain retriever.
*
* @param retriever The entity statement retriever. Must not be
* {@code null}.
* @param constraints The constraints to apply during retrieval. Must
* not be {@code null}.
*/
DefaultTrustChainRetriever(final EntityStatementRetriever retriever,
final TrustChainConstraints constraints) {
if (retriever == null) {
throw new IllegalArgumentException("The entity statement retriever must not be null");
}
this.retriever = retriever;
if (constraints == null) {
throw new IllegalArgumentException("The trust chain constraints must not be null");
}
this.constraints = constraints;
}
/**
* Returns the configured trust chain constraints.
*
* @return The constraints.
*/
public TrustChainConstraints getConstraints() {
return constraints;
}
@Override
public TrustChainSet retrieve(final EntityID target,
final EntityMetadataValidator targetMetadataValidator,
final Set trustAnchors)
throws InvalidEntityMetadataException {
if (CollectionUtils.isEmpty(trustAnchors)) {
throw new IllegalArgumentException("The trust anchors must not be empty");
}
accumulatedExceptions.clear();
accumulatedTrustAnchorJWKSets.clear();
EntityStatement targetStatement;
try {
targetStatement = retriever.fetchEntityConfiguration(target);
} catch (ResolveException e) {
accumulatedExceptions.add(e);
return new TrustChainSet();
}
if (targetMetadataValidator != null) {
EntityType type = targetMetadataValidator.getType();
if (type == null) {
throw new IllegalArgumentException("The target metadata validation doesn't specify a federation entity type");
}
targetMetadataValidator.validate(target, targetStatement.getClaimsSet().getMetadata(type));
}
return retrieve(targetStatement, trustAnchors);
}
@Override
public TrustChainSet retrieve(final EntityStatement targetStatement, final Set trustAnchors) {
if (CollectionUtils.isEmpty(trustAnchors)) {
throw new IllegalArgumentException("The trust anchors must not be empty");
}
accumulatedExceptions.clear();
accumulatedTrustAnchorJWKSets.clear();
List authorityHints = targetStatement.getClaimsSet().getAuthorityHints();
if (CollectionUtils.isEmpty(authorityHints)) {
// Dead end
accumulatedExceptions.add(new ResolveException("Entity " + targetStatement.getEntityID() + " has no authorities listed (authority_hints)"));
return new TrustChainSet();
}
EntityID subject;
try {
subject = EntityID.parse(targetStatement.getClaimsSet().getSubject());
} catch (ParseException e) {
accumulatedExceptions.add(new ResolveException("Entity " + targetStatement.getEntityID() + " subject is illegal: " + e.getMessage(), e));
return new TrustChainSet();
}
Set> anchoredChains = fetchStatementsFromAuthorities(subject, authorityHints, trustAnchors, Collections.emptyList());
TrustChainSet trustChains = new TrustChainSet();
for (List chain: anchoredChains) {
trustChains.add(new TrustChain(targetStatement, chain));
}
return trustChains;
}
/**
* Fetches the entity statement(a) about the given subject from its
* authorities.
*
* @param subject The subject entity. Must not be {@code null}.
* @param authorities The authorities from which to fetch entity
* statements about the subject. Must contain at
* least one.
* @param trustAnchors The configured trust anchors. Immutable. Must
* contain at least one.
* @param partialChain The current partial (non-anchored) entity
* statement chains where newly fetched matching
* entity statements can be appended. Empty for a
* first iteration. Must not be {@code null}.
*
* @return The anchored entity statement chains.
*/
private Set> fetchStatementsFromAuthorities(final EntityID subject,
final List authorities,
final Set trustAnchors,
final List partialChain) {
// Number of updated chains equals number of authority_hints
Set> updatedChains = new HashSet<>();
// The next level of authority hints, keyed by superior entity ID
Map> nextLevelAuthorityHints = new HashMap<>();
for (EntityID authority: authorities) {
if (authority == null) {
continue; // skip
}
if (! constraints.isPermitted(partialChain.size())) {
accumulatedExceptions.add(new ResolveException("Reached max number of intermediates in chain at " + subject));
continue;
}
if (! constraints.isPermitted(authority)) {
accumulatedExceptions.add(new ResolveException("Reached authority which isn't permitted according to constraints: " + authority));
continue;
}
// TODO allowed_leaf_entity_types
EntityStatement superiorEntityConfiguration;
try {
superiorEntityConfiguration = retriever.fetchEntityConfiguration(authority);
nextLevelAuthorityHints.put(authority, superiorEntityConfiguration.getClaimsSet().getAuthorityHints());
} catch (ResolveException e) {
accumulatedExceptions.add(new ResolveException("Couldn't fetch entity configuration from " + authority + ": " + e.getMessage(), e));
continue;
}
if (trustAnchors.contains(superiorEntityConfiguration.getEntityID())) {
accumulatedTrustAnchorJWKSets.put(superiorEntityConfiguration.getEntityID(), superiorEntityConfiguration.getClaimsSet().getJWKSet());
}
FederationEntityMetadata metadata = superiorEntityConfiguration.getClaimsSet().getFederationEntityMetadata();
if (metadata == null) {
accumulatedExceptions.add(new ResolveException("No federation entity metadata for " + authority));
continue;
}
URI fetchEndpointURI = metadata.getFederationFetchEndpointURI();
if (fetchEndpointURI == null) {
accumulatedExceptions.add(new ResolveException("No federation fetch URI in metadata for " + authority));
continue;
}
EntityStatement entityStatement;
try {
entityStatement = retriever.fetchEntityStatement(
fetchEndpointURI,
authority,
subject);
} catch (ResolveException e) {
accumulatedExceptions.add(new ResolveException("Couldn't fetch entity statement from " + fetchEndpointURI + ": " + e.getMessage(), e));
continue;
}
List updatedChain = new LinkedList<>(partialChain);
updatedChain.add(entityStatement);
updatedChains.add(Collections.unmodifiableList(updatedChain));
}
// Find out which chains are now anchored and which still partial
Set> anchoredChains = new LinkedHashSet<>();
Set> remainingPartialChains = new LinkedHashSet<>();
for (List chain: updatedChains) {
EntityStatement last = chain.get(chain.size() - 1);
if (trustAnchors.contains(last.getClaimsSet().getIssuerEntityID())) {
// Reached statement from trust anchor about leaf or intermediate
anchoredChains.add(chain);
} else if (CollectionUtils.isEmpty(last.getClaimsSet().getAuthorityHints())) {
// Reached unknown trust anchor
continue;
} else {
// Add to incomplete chains
remainingPartialChains.add(chain);
}
}
for (List chain: remainingPartialChains) {
EntityStatement last = chain.get(chain.size() - 1);
List nextAuthorities = nextLevelAuthorityHints.get(last.getClaimsSet().getIssuerEntityID());
if (CollectionUtils.isEmpty(nextAuthorities)) {
continue;
}
// Recursion
anchoredChains.addAll(fetchStatementsFromAuthorities(
last.getClaimsSet().getIssuerEntityID(),
nextAuthorities,
trustAnchors,
chain));
}
return anchoredChains;
}
@Override
public Map getAccumulatedTrustAnchorJWKSets() {
return accumulatedTrustAnchorJWKSets;
}
@Override
public List getAccumulatedExceptions() {
return accumulatedExceptions;
}
}