com.nimbusds.openid.connect.sdk.federation.trust.TrustChain 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.security.ProviderException;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import net.jcip.annotations.Immutable;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.oauth2.sdk.id.Subject;
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.FederationMetadataType;
import com.nimbusds.openid.connect.sdk.federation.policy.MetadataPolicy;
import com.nimbusds.openid.connect.sdk.federation.policy.MetadataPolicyEntry;
import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyViolationException;
import com.nimbusds.openid.connect.sdk.federation.policy.operations.DefaultPolicyOperationCombinationValidator;
import com.nimbusds.openid.connect.sdk.federation.policy.operations.PolicyOperationCombinationValidator;
/**
* Federation entity trust chain.
*
* Related specifications:
*
*
* - OpenID Connect Federation 1.0, sections 2.2 and 7.
*
*/
@Immutable
public final class TrustChain {
/**
* The leaf entity self-statement.
*/
private final EntityStatement leaf;
/**
* The superior entity statements.
*/
private final List superiors;
/**
* Caches the resolved expiration time for this trust chain.
*/
private Date exp;
/**
* Creates a new federation entity trust chain. Validates the subject -
* issuer chain, the signatures are not verified.
*
* @param leaf The leaf entity self-statement. Must not be
* {@code null}.
* @param superiors The superior entity statements, starting with a
* statement of the first superior about the leaf,
* ending with the statement of the trust anchor about
* the last intermediate or the leaf (for a minimal
* trust chain). Must contain at least one entity
* statement.
*
* @throws IllegalArgumentException If the subject - issuer chain is
* broken.
*/
public TrustChain(final EntityStatement leaf, List superiors) {
if (leaf == null) {
throw new IllegalArgumentException("The leaf statement must not be null");
}
this.leaf = leaf;
if (CollectionUtils.isEmpty(superiors)) {
throw new IllegalArgumentException("There must be at least one superior statement (issued by the trust anchor)");
}
this.superiors = superiors;
if (! hasValidIssuerSubjectChain(leaf, superiors)) {
throw new IllegalArgumentException("Broken subject - issuer chain");
}
}
private static boolean hasValidIssuerSubjectChain(final EntityStatement leaf, final List superiors) {
Subject nextExpectedSubject = leaf.getClaimsSet().getSubject();
for (EntityStatement superiorStmt : superiors) {
if (! nextExpectedSubject.equals(superiorStmt.getClaimsSet().getSubject())) {
return false;
}
nextExpectedSubject = new Subject(superiorStmt.getClaimsSet().getIssuer().getValue());
}
return true;
}
/**
* Returns the leaf entity self-statement.
*
* @return The leaf entity self-statement.
*/
public EntityStatement getLeafSelfStatement() {
return leaf;
}
/**
* Returns the superior entity statements.
*
* @return The superior entity statements, starting with a statement of
* the first superior about the leaf, ending with the statement
* of the trust anchor about the last intermediate or the leaf
* (for a minimal trust chain).
*/
public List getSuperiorStatements() {
return superiors;
}
/**
* Returns the entity ID of the trust anchor.
*
* @return The entity ID of the trust anchor.
*/
public EntityID getTrustAnchorEntityID() {
// Return last in superiors
return getSuperiorStatements()
.get(getSuperiorStatements().size() - 1)
.getClaimsSet()
.getIssuerEntityID();
}
/**
* Returns the length of this trust chain. A minimal trust chain with a
* leaf and anchor has a length of one.
*
* @return The trust chain length.
*/
public int length() {
return getSuperiorStatements().size();
}
/**
* Resolves the combined metadata policy for this trust chain. Uses the
* {@link DefaultPolicyOperationCombinationValidator default policy
* combination validator}.
*
* @param type The metadata type, such as {@code openid_relying_party}.
* Must not be {@code null}.
*
* @return The combined metadata policy, with no policy operations if
* no policies were found.
*
* @throws PolicyViolationException On a policy violation exception.
*/
public MetadataPolicy resolveCombinedMetadataPolicy(final FederationMetadataType type)
throws PolicyViolationException {
return resolveCombinedMetadataPolicy(type, MetadataPolicyEntry.DEFAULT_POLICY_COMBINATION_VALIDATOR);
}
/**
* Resolves the combined metadata policy for this trust chain.
*
* @param type The metadata type, such as
* {@code openid_relying_party}. Must not
* be {@code null}.
* @param combinationValidator The policy operation combination
* validator. Must not be {@code null}.
*
* @return The combined metadata policy, with no policy operations if
* no policies were found.
*
* @throws PolicyViolationException On a policy violation exception.
*/
public MetadataPolicy resolveCombinedMetadataPolicy(final FederationMetadataType type,
final PolicyOperationCombinationValidator combinationValidator)
throws PolicyViolationException {
List policies = new LinkedList<>();
for (EntityStatement stmt: getSuperiorStatements()) {
MetadataPolicy metadataPolicy = stmt.getClaimsSet().getMetadataPolicy(type);
if (metadataPolicy == null) {
continue;
}
policies.add(metadataPolicy);
}
return MetadataPolicy.combine(policies, combinationValidator);
}
/**
* Return an iterator starting from the leaf entity statement.
*
* @return The iterator.
*/
public Iterator iteratorFromLeaf() {
// Init
final AtomicReference next = new AtomicReference<>(getLeafSelfStatement());
final Iterator superiorsIterator = getSuperiorStatements().iterator();
return new Iterator() {
@Override
public boolean hasNext() {
return next.get() != null;
}
@Override
public EntityStatement next() {
EntityStatement toReturn = next.get();
if (toReturn == null) {
return null; // reached end on last iteration
}
// Set statement to return on next iteration
if (toReturn.equals(getLeafSelfStatement())) {
// Return first superior
next.set(superiorsIterator.next());
} else {
// Return next superior or end
if (superiorsIterator.hasNext()) {
next.set(superiorsIterator.next());
} else {
next.set(null);
}
}
return toReturn;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Resolves the expiration time for this trust chain. Equals the
* nearest expiration when all entity statements in the trust chain are
* considered.
*
* @return The expiration time for this trust chain.
*/
public Date resolveExpirationTime() {
if (exp != null) {
return exp;
}
Iterator it = iteratorFromLeaf();
Date nearestExp = null;
while (it.hasNext()) {
Date stmtExp = it.next().getClaimsSet().getExpirationTime();
if (nearestExp == null) {
nearestExp = stmtExp; // on first iteration
} else if (stmtExp.before(nearestExp)) {
nearestExp = stmtExp; // replace nearest
}
}
exp = nearestExp;
return exp;
}
/**
* Verifies the signatures in this trust chain.
*
* @param trustAnchorJWKSet The trust anchor JWK set. Must not be
* {@code null}.
*
* @throws BadJOSEException If a signature is invalid or a statement is
* expired or before the issue time.
* @throws JOSEException On a internal JOSE exception.
*/
public void verifySignatures(final JWKSet trustAnchorJWKSet)
throws BadJOSEException, JOSEException {
Base64URL signingJWKThumbprint;
try {
signingJWKThumbprint = leaf.verifySignatureOfSelfStatement();
} catch (BadJOSEException e) {
throw new BadJOSEException("Invalid leaf statement: " + e.getMessage(), e);
}
for (int i=0; i < superiors.size(); i++) {
EntityStatement stmt = superiors.get(i);
JWKSet verificationJWKSet;
if (i+1 == superiors.size()) {
verificationJWKSet = trustAnchorJWKSet;
} else {
verificationJWKSet = superiors.get(i+1).getClaimsSet().getJWKSet();
}
// Check that the signing JWK is registered with the superior
if (! hasJWKWithThumbprint(stmt.getClaimsSet().getJWKSet(), signingJWKThumbprint)) {
throw new BadJOSEException("Signing JWK with thumbprint " + signingJWKThumbprint + " not found in entity statement issued from superior " + stmt.getClaimsSet().getIssuerEntityID());
}
try {
signingJWKThumbprint = stmt.verifySignature(verificationJWKSet);
} catch (BadJOSEException e) {
throw new BadJOSEException("Invalid statement from " + stmt.getClaimsSet().getIssuer() + ": " + e.getMessage(), e);
}
}
}
private static boolean hasJWKWithThumbprint(final JWKSet jwkSet, final Base64URL thumbprint) {
if (jwkSet == null) {
return false;
}
for (JWK jwk: jwkSet.getKeys()) {
try {
if (thumbprint.equals(jwk.computeThumbprint())) {
return true;
}
} catch (JOSEException e) {
throw new ProviderException(e.getMessage(), e);
}
}
return false;
}
}