All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.openid.connect.sdk.federation.trust.TrustChain Maven / Gradle / Ivy

Go to download

OAuth 2.0 SDK with OpenID Connection extensions for developing client and server applications.

There is a newer version: 11.19.1
Show newest version
/*
 * 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.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.id.Subject;
import com.nimbusds.oauth2.sdk.util.CollectionUtils;
import com.nimbusds.oauth2.sdk.util.ListUtils;
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.EntityType;
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 3.2 and 7.1. *
*/ @Immutable public final class TrustChain { /** * The leaf entity configuration. */ private final EntityStatement leaf; /** * The superior entity statements. */ private final List superiors; /** * The optional trust anchor entity configuration. */ private final EntityStatement trustAnchor; /** * Caches the resolved expiration time for this trust chain. */ private Date exp; /** * Creates a new trust chain. Validates the subject - issuer chain, the * signatures are not verified. * * @param leaf The leaf entity configuration. 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, final List superiors) { this(leaf, superiors, null); } /** * Creates a new trust chain. Validates the subject - issuer chain, the * signatures are not verified. * * @param leaf The leaf entity configuration. 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. * @param trustAnchor The optional trust anchor entity configuration, * {@code null} if not specified. * * @throws IllegalArgumentException If the subject - issuer chain is * broken. */ public TrustChain(final EntityStatement leaf, final List superiors, final EntityStatement trustAnchor) { // leaf config checks if (leaf == null) { throw new IllegalArgumentException("The leaf entity configuration must not be null"); } if (! leaf.getClaimsSet().isSelfStatement()) { throw new IllegalArgumentException("The leaf entity configuration must be a self-statement"); } this.leaf = leaf; // superior statements check if (CollectionUtils.isEmpty(superiors)) { throw new IllegalArgumentException("There must be at least one superior statement (issued by the trust anchor)"); } this.superiors = superiors; // optional trust anchor config checks this.trustAnchor = trustAnchor; if (trustAnchor != null && ! trustAnchor.getClaimsSet().isSelfStatement()) { throw new IllegalArgumentException("The trust anchor entity configuration must be a self-statement"); } if (! hasValidIssuerSubjectChain(leaf, superiors, trustAnchor)) { throw new IllegalArgumentException("Broken subject - issuer chain"); } } private static boolean hasValidIssuerSubjectChain(final EntityStatement leaf, final List superiors, final EntityStatement trustAnchor) { Subject nextExpectedSubject = leaf.getClaimsSet().getSubject(); for (EntityStatement superiorStmt : superiors) { if (! nextExpectedSubject.equals(superiorStmt.getClaimsSet().getSubject())) { return false; // chain breaks } nextExpectedSubject = new Subject(superiorStmt.getClaimsSet().getIssuer().getValue()); } if (trustAnchor == null) { // No optional trust anchor config return true; } // The last issuer in the chain is the trust anchor EntityStatement topSuperior = superiors.get(superiors.size() - 1); return topSuperior.getClaimsSet().getIssuer().equals(trustAnchor.getClaimsSet().getIssuer()); } /** * Returns the leaf entity configuration. * * @return The leaf entity configuration. */ public EntityStatement getLeafConfiguration() { 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 optional trust anchor entity configuration. * * @return The trust anchor entity configuration, {@code null} if not * specified. */ public EntityStatement getTrustAnchorConfiguration() { return trustAnchor; } /** * 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, with a minimal length of one. */ 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 entity 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 EntityType type) throws PolicyViolationException { return resolveCombinedMetadataPolicy(type, MetadataPolicyEntry.DEFAULT_POLICY_COMBINATION_VALIDATOR); } /** * Resolves the combined metadata policy for this trust chain. * * @param type The entity 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 EntityType 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. The * optional trust anchor entity configuration is omitted. * * @return The iterator. */ public Iterator iteratorFromLeaf() { // Init final AtomicReference next = new AtomicReference<>(leaf); final Iterator superiorsIterator = superiors.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(leaf)) { // 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 next * expiration in time 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 an 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 entity configuration: " + 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); } } if (trustAnchor != null) { if (! hasJWKWithThumbprint(trustAnchor.getClaimsSet().getJWKSet(), signingJWKThumbprint)) { throw new BadJOSEException("Signing JWK with thumbprint " + signingJWKThumbprint + " not found in trust anchor entity configuration"); } try { trustAnchor.verifySignatureOfSelfStatement(); } catch (BadJOSEException e) { throw new BadJOSEException("Invalid trust anchor entity configuration: " + 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; } /** * Returns a JWT list representation of this trust chain. * * @return The JWT list. */ public List toJWTs() { List out = new LinkedList<>(); out.add(leaf.getSignedStatement()); for (EntityStatement s: superiors) { out.add(s.getSignedStatement()); } if (trustAnchor != null) { out.add(trustAnchor.getSignedStatement()); } return out; } /** * Returns a serialised JWT list representation of this trust chain. * * @return The serialised JWT list. */ public List toSerializedJWTs() { List out = new LinkedList<>(); for (SignedJWT jwt: toJWTs()) { out.add(jwt.serialize()); } return out; } /** * Parses a trust chain from the specified JWT list. * * @param statementJWTs The JWT list. Must not be {@code null}. * * @return The trust chain. * * @throws ParseException If parsing failed. */ public static TrustChain parse(final List statementJWTs) throws ParseException { if (statementJWTs.size() < 2) { throw new ParseException("There must be at least 2 statement JWTs"); } EntityStatement leaf = null; List superiors = new LinkedList<>(); EntityStatement trustAnchor = null; for (SignedJWT jwt: ListUtils.removeNullItems(statementJWTs)) { if (leaf == null) { try { leaf = EntityStatement.parse(jwt); } catch (ParseException e) { throw new ParseException("Invalid leaf entity configuration: " + e.getMessage(), e); } } else { EntityStatement statement; try { statement = EntityStatement.parse(jwt); } catch (ParseException e) { throw new ParseException("Invalid superior entity statement: " + e.getMessage(), e); } if (! statement.getClaimsSet().isSelfStatement()) { superiors.add(statement); } else { trustAnchor = statement; // assume optional TA config } } } try { return new TrustChain(leaf, superiors, trustAnchor); } catch (Exception e) { throw new ParseException("Illegal trust chain: " + e.getMessage(), e); } } /** * Parses a trust chain from the specified serialised JWT list. * * @param statementJWTs The serialised JWT list. Must not be * {@code null}. * * @return The trust chain. * * @throws ParseException If parsing failed. */ public static TrustChain parseSerialized(final List statementJWTs) throws ParseException { List jwtList = new LinkedList<>(); for (String s: ListUtils.removeNullItems(statementJWTs)) { try { jwtList.add(SignedJWT.parse(s)); } catch (java.text.ParseException e) { throw new ParseException("Invalid JWT in trust chain: " + e.getMessage(), e); } } return parse(jwtList); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy