001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2020, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.openid.connect.sdk.federation.trust; 019 020 021import java.util.Date; 022import java.util.Iterator; 023import java.util.List; 024import java.util.concurrent.atomic.AtomicReference; 025 026import net.jcip.annotations.Immutable; 027 028import com.nimbusds.jose.JOSEException; 029import com.nimbusds.jose.jwk.JWKSet; 030import com.nimbusds.jose.proc.BadJOSEException; 031import com.nimbusds.oauth2.sdk.id.Subject; 032import com.nimbusds.oauth2.sdk.util.CollectionUtils; 033import com.nimbusds.openid.connect.sdk.federation.entities.EntityID; 034import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement; 035 036 037/** 038 * Federation entity trust chain. 039 * 040 * <p>Related specifications: 041 * 042 * <ul> 043 * <li>OpenID Connect Federation 1.0, section 2.2. 044 * </ul> 045 */ 046@Immutable 047public final class TrustChain { 048 049 050 /** 051 * The leaf entity self-statement. 052 */ 053 private final EntityStatement leaf; 054 055 056 /** 057 * The superior entity statements. 058 */ 059 private final List<EntityStatement> superiors; 060 061 062 /** 063 * Caches the resolved expiration time for this trust chain. 064 */ 065 private Date exp; 066 067 068 /** 069 * Creates a new federation entity trust chain. Validates the subject - 070 * issuer chain, the signatures are not verified. 071 * 072 * @param leaf The leaf entity self-statement. Must not be 073 * {@code null}. 074 * @param superiors The superior entity statements, starting with a 075 * statement of the first superior about the leaf, 076 * ending with the statement of the trust anchor about 077 * the last intermediate or the leaf (for a minimal 078 * trust chain). Must contain at least one entity 079 * statement. 080 * 081 * @throws IllegalArgumentException If the subject - issuer chain is 082 * broken. 083 */ 084 public TrustChain(final EntityStatement leaf, List<EntityStatement> superiors) { 085 if (leaf == null) { 086 throw new IllegalArgumentException("The leaf statement must not be null"); 087 } 088 this.leaf = leaf; 089 090 if (CollectionUtils.isEmpty(superiors)) { 091 throw new IllegalArgumentException("There must be at least one superior statement (issued by the trust anchor)"); 092 } 093 this.superiors = superiors; 094 if (! hasValidIssuerSubjectChain(leaf, superiors)) { 095 throw new IllegalArgumentException("Broken subject - issuer chain"); 096 } 097 } 098 099 100 private static boolean hasValidIssuerSubjectChain(final EntityStatement leaf, final List<EntityStatement> superiors) { 101 102 Subject nextExpectedSubject = leaf.getClaimsSet().getSubject(); 103 104 for (EntityStatement superiorStmt : superiors) { 105 if (! nextExpectedSubject.equals(superiorStmt.getClaimsSet().getSubject())) { 106 return false; 107 } 108 nextExpectedSubject = new Subject(superiorStmt.getClaimsSet().getIssuer().getValue()); 109 } 110 return true; 111 } 112 113 114 /** 115 * Returns the leaf entity self-statement. 116 * 117 * @return The leaf entity self-statement. 118 */ 119 public EntityStatement getLeafSelfStatement() { 120 return leaf; 121 } 122 123 124 /** 125 * Returns the superior entity statements. 126 * 127 * @return The superior entity statements, starting with a statement of 128 * the first superior about the leaf, ending with the statement 129 * of the trust anchor about the last intermediate or the leaf 130 * (for a minimal trust chain). 131 */ 132 public List<EntityStatement> getSuperiorStatements() { 133 return superiors; 134 } 135 136 137 /** 138 * Returns the entity ID of the trust anchor. 139 * 140 * @return The entity ID of the trust anchor. 141 */ 142 public EntityID getTrustAnchorEntityID() { 143 144 // Return last in superiors 145 return getSuperiorStatements() 146 .get(getSuperiorStatements().size() - 1) 147 .getClaimsSet() 148 .getIssuerEntityID(); 149 } 150 151 152 /** 153 * Returns the length of this trust chain. A minimal trust chain with a 154 * leaf and anchor has a length of one. 155 * 156 * @return The trust chain length. 157 */ 158 public int length() { 159 160 return getSuperiorStatements().size(); 161 } 162 163 164 /** 165 * Return an iterator starting from the leaf entity statement. 166 * 167 * @return The iterator. 168 */ 169 public Iterator<EntityStatement> iteratorFromLeaf() { 170 171 // Init 172 final AtomicReference<EntityStatement> next = new AtomicReference<>(getLeafSelfStatement()); 173 final Iterator<EntityStatement> superiorsIterator = getSuperiorStatements().iterator(); 174 175 return new Iterator<EntityStatement>() { 176 @Override 177 public boolean hasNext() { 178 return next.get() != null; 179 } 180 181 182 @Override 183 public EntityStatement next() { 184 EntityStatement toReturn = next.get(); 185 if (toReturn == null) { 186 return null; // reached end on last iteration 187 } 188 189 // Set statement to return on next iteration 190 if (toReturn.equals(getLeafSelfStatement())) { 191 // Return first superior 192 next.set(superiorsIterator.next()); 193 } else { 194 // Return next superior or end 195 if (superiorsIterator.hasNext()) { 196 next.set(superiorsIterator.next()); 197 } else { 198 next.set(null); 199 } 200 } 201 202 return toReturn; 203 } 204 205 206 @Override 207 public void remove() { 208 throw new UnsupportedOperationException(); 209 } 210 }; 211 } 212 213 214 /** 215 * Resolves the expiration time for this trust chain. Equals the 216 * nearest expiration when all entity statements in the trust chain are 217 * considered. 218 * 219 * @return The expiration time for this trust chain. 220 */ 221 public Date resolveExpirationTime() { 222 223 if (exp != null) { 224 return exp; 225 } 226 227 Iterator<EntityStatement> it = iteratorFromLeaf(); 228 229 Date nearestExp = null; 230 231 while (it.hasNext()) { 232 233 Date stmtExp = it.next().getClaimsSet().getExpirationTime(); 234 235 if (nearestExp == null) { 236 nearestExp = stmtExp; // on first iteration 237 } else if (stmtExp.before(nearestExp)) { 238 nearestExp = stmtExp; // replace nearest 239 } 240 } 241 242 exp = nearestExp; 243 return exp; 244 } 245 246 247 /** 248 * Verifies the signatures in this trust chain. 249 * 250 * @param trustAnchorJWKSet The trust anchor JWK set. Must not be 251 * {@code null}. 252 * 253 * @throws BadJOSEException If a signature is invalid or a statement is 254 * expired or before the issue time. 255 * @throws JOSEException On a internal JOSE exception. 256 */ 257 public void verifySignatures(final JWKSet trustAnchorJWKSet) 258 throws BadJOSEException, JOSEException { 259 260 try { 261 leaf.verifySignatureOfSelfStatement(); 262 } catch (BadJOSEException e) { 263 throw new BadJOSEException("Invalid leaf statement: " + e.getMessage(), e); 264 } 265 266 for (int i=0; i < superiors.size(); i++) { 267 268 EntityStatement stmt = superiors.get(i); 269 270 JWKSet verificationJWKSet; 271 if (i+1 == superiors.size()) { 272 verificationJWKSet = trustAnchorJWKSet; 273 } else { 274 verificationJWKSet = superiors.get(i+1).getClaimsSet().getJWKSet(); 275 } 276 277 try { 278 stmt.verifySignature(verificationJWKSet); 279 } catch (BadJOSEException e) { 280 throw new BadJOSEException("Invalid statement from " + stmt.getClaimsSet().getIssuer() + ": " + e.getMessage(), e); 281 } 282 } 283 } 284}