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.*; 022 023import com.nimbusds.jose.JOSEException; 024import com.nimbusds.jose.jwk.JWKSet; 025import com.nimbusds.jose.proc.BadJOSEException; 026import com.nimbusds.oauth2.sdk.util.MapUtils; 027import com.nimbusds.openid.connect.sdk.federation.entities.EntityID; 028import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement; 029import com.nimbusds.openid.connect.sdk.federation.trust.constraints.TrustChainConstraints; 030 031 032/** 033 * Trust chain resolver. 034 * 035 * <p>Related specifications: 036 * 037 * <ul> 038 * <li>OpenID Connect Federation 1.0, section 7. 039 * </ul> 040 */ 041public class TrustChainResolver { 042 043 044 /** 045 * The configured trust anchors with their public JWK sets. 046 */ 047 private final Map<EntityID, JWKSet> trustAnchors; 048 049 050 /** 051 * The entity statement retriever. 052 */ 053 private final EntityStatementRetriever statementRetriever; 054 055 056 /** 057 * The trust chain constraints. 058 */ 059 private final TrustChainConstraints constraints; 060 061 062 /** 063 * Creates a new trust chain resolver with a single trust anchor, with 064 * {@link TrustChainConstraints#NO_CONSTRAINTS no trust chain 065 * constraints}. 066 * 067 * @param trustAnchor The trust anchor. Must not be {@code null}. 068 */ 069 public TrustChainResolver(final EntityID trustAnchor) { 070 this(trustAnchor, null); 071 } 072 073 074 /** 075 * Creates a new trust chain resolver with a single trust anchor, with 076 * {@link TrustChainConstraints#NO_CONSTRAINTS no trust chain 077 * constraints}. 078 * 079 * @param trustAnchor The trust anchor. Must not be {@code null}. 080 * @param trustAnchorJWKSet The trust anchor public JWK set, 081 * {@code null} if not available. 082 */ 083 public TrustChainResolver(final EntityID trustAnchor, 084 final JWKSet trustAnchorJWKSet) { 085 this( 086 Collections.singletonMap(trustAnchor, trustAnchorJWKSet), 087 TrustChainConstraints.NO_CONSTRAINTS, 088 new DefaultEntityStatementRetriever() 089 ); 090 } 091 092 093 /** 094 * Creates a new trust chain resolver with multiple trust anchors, with 095 * {@link TrustChainConstraints#NO_CONSTRAINTS no trust chain 096 * constraints}. 097 * 098 * @param trustAnchors The trust anchors with their public JWK 099 * sets (if available). Must contain at 100 * least one anchor. 101 * @param httpConnectTimeoutMs The HTTP connect timeout in 102 * milliseconds, zero means timeout 103 * determined by the underlying HTTP 104 * client. 105 * @param httpReadTimeoutMs The HTTP read timeout in milliseconds, 106 * zero means timout determined by the 107 * underlying HTTP client. 108 */ 109 public TrustChainResolver(final Map<EntityID, JWKSet> trustAnchors, 110 final int httpConnectTimeoutMs, 111 final int httpReadTimeoutMs) { 112 this( 113 trustAnchors, 114 TrustChainConstraints.NO_CONSTRAINTS, 115 new DefaultEntityStatementRetriever(httpConnectTimeoutMs, httpReadTimeoutMs) 116 ); 117 } 118 119 120 /** 121 * Creates new trust chain resolver. 122 * 123 * @param trustAnchors The trust anchors with their public JWK 124 * sets. Must contain at least one anchor. 125 * @param statementRetriever The entity statement retriever to use. 126 * Must not be {@code null}. 127 */ 128 public TrustChainResolver(final Map<EntityID, JWKSet> trustAnchors, 129 final TrustChainConstraints constraints, 130 final EntityStatementRetriever statementRetriever) { 131 132 if (MapUtils.isEmpty(trustAnchors)) { 133 throw new IllegalArgumentException("The trust anchors map must not be empty or null"); 134 } 135 this.trustAnchors = trustAnchors; 136 137 if (constraints == null) { 138 throw new IllegalArgumentException("The trust chain constraints must not be null"); 139 } 140 this.constraints = constraints; 141 142 if (statementRetriever == null) { 143 throw new IllegalArgumentException("The entity statement retriever must not be null"); 144 } 145 this.statementRetriever = statementRetriever; 146 } 147 148 149 /** 150 * Returns the configured trust anchors. 151 * 152 * @return The trust anchors with their public JWK sets (if available). 153 * Contains at least one anchor. 154 */ 155 public Map<EntityID, JWKSet> getTrustAnchors() { 156 return Collections.unmodifiableMap(trustAnchors); 157 } 158 159 160 /** 161 * Returns the configured entity statement retriever. 162 * 163 * @return The entity statement retriever. 164 */ 165 public EntityStatementRetriever getEntityStatementRetriever() { 166 return statementRetriever; 167 } 168 169 170 /** 171 * Returns the configured trust chain constraints. 172 * 173 * @return The constraints. 174 */ 175 public TrustChainConstraints getConstraints() { 176 return constraints; 177 } 178 179 180 /** 181 * Resolves the trust chains for the specified target. 182 * 183 * @param target The target. Must not be {@code null}. 184 * 185 * @return The resolved trust chains, containing at least one valid and 186 * verified chain. 187 * 188 * @throws ResolveException If no trust chain could be resolved. 189 */ 190 public TrustChainSet resolveTrustChains(final EntityID target) 191 throws ResolveException { 192 193 if (trustAnchors.get(target) != null) { 194 throw new ResolveException("Target is trust anchor"); 195 } 196 197 TrustChainRetriever retriever = new DefaultTrustChainRetriever(statementRetriever, constraints); 198 Set<TrustChain> fetchedTrustChains = retriever.retrieve(target, trustAnchors.keySet()); 199 return verifyTrustChains( 200 fetchedTrustChains, 201 retriever.getAccumulatedTrustAnchorJWKSets(), 202 retriever.getAccumulatedExceptions()); 203 } 204 205 206 /** 207 * Resolves the trust chains for the specified target. 208 * 209 * @param targetStatement The target entity statement. Must not be 210 * {@code null}. 211 * 212 * @return The resolved trust chains, containing at least one valid and 213 * verified chain. 214 * 215 * @throws ResolveException If no trust chain could be resolved. 216 */ 217 public TrustChainSet resolveTrustChains(final EntityStatement targetStatement) 218 throws ResolveException { 219 220 if (trustAnchors.get(targetStatement.getEntityID()) != null) { 221 throw new ResolveException("Target is trust anchor"); 222 } 223 224 TrustChainRetriever retriever = new DefaultTrustChainRetriever(statementRetriever, constraints); 225 Set<TrustChain> fetchedTrustChains = retriever.retrieve(targetStatement, trustAnchors.keySet()); 226 return verifyTrustChains( 227 fetchedTrustChains, 228 retriever.getAccumulatedTrustAnchorJWKSets(), 229 retriever.getAccumulatedExceptions()); 230 } 231 232 233 /** 234 * Verifies the specified fetched trust chains. 235 * 236 * @param fetchedTrustChains The fetched trust chains. Must 237 * not be {@code null}, 238 * @param accumulatedTrustAnchorJWKSets The accumulated trust anchor(s) 239 * JWK sets, empty if none. Must 240 * not be {@code null}. 241 * @param accumulatedExceptions The accumulated exceptions, 242 * empty if none. Must not be 243 * {@code null}. 244 * @return The verified trust chain set. 245 * 246 * @throws ResolveException If no trust chain could be verified. 247 */ 248 private TrustChainSet verifyTrustChains(final Set<TrustChain> fetchedTrustChains, 249 final Map<EntityID, JWKSet> accumulatedTrustAnchorJWKSets, 250 final List<Throwable> accumulatedExceptions) 251 throws ResolveException { 252 253 if (fetchedTrustChains.isEmpty()) { 254 if (accumulatedExceptions.isEmpty()) { 255 throw new ResolveException("No trust chain leading up to a trust anchor"); 256 } else if (accumulatedExceptions.size() == 1){ 257 Throwable cause = accumulatedExceptions.get(0); 258 throw new ResolveException("Couldn't resolve trust chain: " + cause.getMessage(), cause); 259 } else { 260 throw new ResolveException("Couldn't resolve trust chain due to multiple causes", accumulatedExceptions); 261 } 262 } 263 264 List<Throwable> verificationExceptions = new LinkedList<>(); 265 266 TrustChainSet verifiedTrustChains = new TrustChainSet(); 267 268 for (TrustChain chain: fetchedTrustChains) { 269 270 EntityID anchor = chain.getTrustAnchorEntityID(); 271 JWKSet anchorJWKSet = trustAnchors.get(anchor); 272 if (anchorJWKSet == null) { 273 anchorJWKSet = accumulatedTrustAnchorJWKSets.get(anchor); 274 } 275 276 try { 277 chain.verifySignatures(anchorJWKSet); 278 } catch (BadJOSEException | JOSEException e) { 279 verificationExceptions.add(e); 280 continue; 281 } 282 283 verifiedTrustChains.add(chain); 284 } 285 286 if (verifiedTrustChains.isEmpty()) { 287 288 List<Throwable> moreAccumulatedExceptions = new LinkedList<>(accumulatedExceptions); 289 moreAccumulatedExceptions.addAll(verificationExceptions); 290 291 if (verificationExceptions.size() == 1) { 292 Throwable cause = verificationExceptions.get(0); 293 throw new ResolveException("Couldn't resolve trust chain: " + cause.getMessage(), moreAccumulatedExceptions); 294 } else { 295 throw new ResolveException("Couldn't resolve trust chain due to multiple causes", moreAccumulatedExceptions); 296 } 297 } 298 299 return verifiedTrustChains; 300 } 301}