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; 028 029 030/** 031 * Trust chain resolver. 032 * 033 * <p>Related specifications: 034 * 035 * <ul> 036 * <li>OpenID Connect Federation 1.0, section 7. 037 * </ul> 038 */ 039public class TrustChainResolver { 040 041 042 /** 043 * The configured trust anchors with their public JWK sets. 044 */ 045 private final Map<EntityID, JWKSet> trustAnchors; 046 047 048 /** 049 * The entity statement retriever. 050 */ 051 private final EntityStatementRetriever statementRetriever; 052 053 054 /** 055 * Creates a new trust chain resolver with a single trust anchor. 056 * 057 * @param trustAnchor The trust anchor. Must not be {@code null}. 058 */ 059 public TrustChainResolver(final EntityID trustAnchor) { 060 this(trustAnchor, null); 061 } 062 063 064 /** 065 * Creates a new trust chain resolver with a single trust anchor. 066 * 067 * @param trustAnchor The trust anchor. Must not be {@code null}. 068 * @param trustAnchorJWKSet The trust anchor public JWK set, 069 * {@code null} if not available. 070 */ 071 public TrustChainResolver(final EntityID trustAnchor, 072 final JWKSet trustAnchorJWKSet) { 073 this(Collections.singletonMap(trustAnchor, trustAnchorJWKSet), new DefaultEntityStatementRetriever()); 074 } 075 076 077 /** 078 * Creates a new trust chain resolver with multiple trust anchors. 079 * 080 * @param trustAnchors The trust anchors with their public JWK 081 * sets (if available). Must contain at 082 * least one anchor. 083 * @param httpConnectTimeoutMs The HTTP connect timeout in 084 * milliseconds, zero means timeout 085 * determined by the underlying HTTP 086 * client. 087 * @param httpReadTimeoutMs The HTTP read timeout in milliseconds, 088 * zero means timout determined by the 089 * underlying HTTP client. 090 */ 091 public TrustChainResolver(final Map<EntityID, JWKSet> trustAnchors, 092 final int httpConnectTimeoutMs, 093 final int httpReadTimeoutMs) { 094 this(trustAnchors, new DefaultEntityStatementRetriever(httpConnectTimeoutMs, httpReadTimeoutMs)); 095 } 096 097 098 /** 099 * Creates new trust chain resolver. 100 * 101 * @param trustAnchors The trust anchors with their public JWK 102 * sets. Must contain at least one anchor. 103 * @param statementRetriever The entity statement retriever to use. 104 * Must not be {@code null}. 105 */ 106 public TrustChainResolver(final Map<EntityID, JWKSet> trustAnchors, 107 final EntityStatementRetriever statementRetriever) { 108 if (MapUtils.isEmpty(trustAnchors)) { 109 throw new IllegalArgumentException("The trust anchors map must not be empty or null"); 110 } 111 this.trustAnchors = trustAnchors; 112 113 if (statementRetriever == null) { 114 throw new IllegalArgumentException("The entity statement retriever must not be null"); 115 } 116 this.statementRetriever = statementRetriever; 117 } 118 119 120 /** 121 * Returns the configured trust anchors. 122 * 123 * @return The trust anchors with their public JWK sets (if available). 124 * Contains at least one anchor. 125 */ 126 public Map<EntityID, JWKSet> getTrustAnchors() { 127 return Collections.unmodifiableMap(trustAnchors); 128 } 129 130 131 /** 132 * Returns the configured entity statement retriever. 133 * 134 * @return The configured entity statement retriever. 135 */ 136 public EntityStatementRetriever getEntityStatementRetriever() { 137 return statementRetriever; 138 } 139 140 141 /** 142 * Resolves the trust chains for the specified target. 143 * 144 * @param target The target. Must not be {@code null}. 145 * 146 * @return The resolved trust chains, containing at least one valid and 147 * verified chain. 148 * 149 * @throws ResolveException If no trust chain could be resolved. 150 */ 151 public TrustChainSet resolveTrustChains(final EntityID target) 152 throws ResolveException { 153 154 if (trustAnchors.get(target) != null) { 155 throw new ResolveException("Target is trust anchor"); 156 } 157 158 TrustChainRetriever retriever = new DefaultTrustChainRetriever(statementRetriever); 159 160 Set<TrustChain> fetchedTrustChains = retriever.retrieve(target, trustAnchors.keySet()); 161 162 if (fetchedTrustChains.isEmpty()) { 163 164 if (retriever.getAccumulatedExceptions().isEmpty()) { 165 throw new ResolveException("No trust chain leading up to a trust anchor"); 166 } else if (retriever.getAccumulatedExceptions().size() == 1){ 167 Throwable cause = retriever.getAccumulatedExceptions().get(0); 168 throw new ResolveException("Couldn't resolve trust chain: " + cause.getMessage(), cause); 169 } else { 170 throw new ResolveException("Couldn't resolve trust chain due to multiple causes", retriever.getAccumulatedExceptions()); 171 } 172 } 173 174 List<Throwable> verificationExceptions = new LinkedList<>(); 175 176 TrustChainSet verifiedTrustChains = new TrustChainSet(); 177 178 for (TrustChain chain: fetchedTrustChains) { 179 180 EntityID anchor = chain.getTrustAnchorEntityID(); 181 JWKSet anchorJWKSet = trustAnchors.get(anchor); 182 if (anchorJWKSet == null) { 183 anchorJWKSet = retriever.getAccumulatedTrustAnchorJWKSets().get(anchor); 184 } 185 186 try { 187 chain.verifySignatures(anchorJWKSet); 188 } catch (BadJOSEException | JOSEException e) { 189 verificationExceptions.add(e); 190 continue; 191 } 192 193 verifiedTrustChains.add(chain); 194 } 195 196 if (verifiedTrustChains.isEmpty()) { 197 198 List<Throwable> accumulatedExceptions = new LinkedList<>(retriever.getAccumulatedExceptions()); 199 accumulatedExceptions.addAll(verificationExceptions); 200 201 if (verificationExceptions.size() == 1) { 202 Throwable cause = verificationExceptions.get(0); 203 throw new ResolveException("Couldn't resolve trust chain: " + cause.getMessage(), accumulatedExceptions); 204 } else { 205 throw new ResolveException("Couldn't resolve trust chain due to multiple causes", accumulatedExceptions); 206 } 207 } 208 209 return verifiedTrustChains; 210 } 211}