1 /* 2 * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.] 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.opensaml.xml.security.keyinfo; 18 19 import java.security.Key; 20 import java.security.KeyException; 21 import java.security.PrivateKey; 22 import java.security.PublicKey; 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.HashSet; 26 import java.util.List; 27 import java.util.Set; 28 29 import javax.crypto.SecretKey; 30 31 import org.opensaml.xml.XMLObject; 32 import org.opensaml.xml.security.CriteriaSet; 33 import org.opensaml.xml.security.SecurityException; 34 import org.opensaml.xml.security.SecurityHelper; 35 import org.opensaml.xml.security.credential.AbstractCriteriaFilteringCredentialResolver; 36 import org.opensaml.xml.security.credential.BasicCredential; 37 import org.opensaml.xml.security.credential.Credential; 38 import org.opensaml.xml.signature.KeyInfo; 39 import org.opensaml.xml.signature.KeyName; 40 import org.opensaml.xml.signature.KeyValue; 41 import org.slf4j.Logger; 42 import org.slf4j.LoggerFactory; 43 44 /** 45 * Implementation of {@link KeyInfoCredentialResolver} which resolves credentials based on a {@link KeyInfo} element 46 * using a configured list of {@link KeyInfoProvider}'s and optional post-processing hooks. 47 * 48 * <p> 49 * The majority of the processing of the KeyInfo and extraction of {@link Credential}'s from the KeyInfo is handled by 50 * instances of {@link KeyInfoProvider}. An ordered list of KeyInfoProviders must be supplied to the resolver when it 51 * is constructed. 52 * </p> 53 * 54 * <p> 55 * This resolver requires a {@link KeyInfoCriteria} to be supplied as the resolution criteria. It is permissible, 56 * however, for the criteria's KeyInfo data object to be null. This allows for more convenient processing logic, for 57 * example, in cases when a parent element allows an optional KeyInfo and when in fact a given instance does not contain 58 * one. Specialized subclasses of this resolver may still attempt to return credentials in an implementation or 59 * context-specific manner, as described below. 60 * </p> 61 * 62 * <p> 63 * Processing of the supplied KeyInfo element proceeds as follows: 64 * <ol> 65 * <li>A {@link KeyInfoResolutionContext} is instantiated. This resolution context is used to hold state shared amongst 66 * all the providers and processing hooks which run within the resolver.</li> 67 * <li>This resolution context is initialized and populated with the actual KeyInfo object being processed as well as 68 * the values of any {@link KeyName} child elements present.</li> 69 * <li>An attempt is then made to resolve a credential from any {@link KeyValue} child elements as described for 70 * {@link #resolveKeyValue(KeyInfoResolutionContext, CriteriaSet, List)} If a credential is so resolved, its key will 71 * also be placed in the resolution context</li> 72 * <li>The remaining (non-KeyValue) children are then processed in document order. Each child element is processed by 73 * the registered providers in provider list order. The credential or credentials resolved by the first provider to 74 * successfully do so are added to the effective set of credentials returned by the resolver, and processing of that 75 * child element terminates. Processing continues with the next child element.</li> 76 * <li>At this point all KeyInfo children have been processed. If the effective set of credentials to return is empty, 77 * and if a key was resolved from a KeyValue element and is available in the resolution context, a basic credential is 78 * built with that key and is added to the effective set. Since the KeyInfo may have a plain KeyValue representation of 79 * the key represented by the KeyInfo, in addition to a more specific key type/container (and hence credential) 80 * representation, this technique avoids the unnecessary return of duplicate keys, returning only the more specific 81 * credential representation of the key.</li> 82 * <li>A post-processing hook is then called: {@link #postProcess(KeyInfoResolutionContext, CriteriaSet, List)}. The 83 * default implementation is a no-op. This is an extension point by which subclasses may implement custom 84 * post-processing of the effective credential set to be returned. One example use case is when the KeyInfo being 85 * processed represents the public aspects (e.g. public key, or a key name or other identifier) of an encryption key 86 * belonging to the resolving entity. The resolved public keys and other resolution context information may be used to 87 * further resolve the credential or credentials containing the associated decryption key (i.e. a private or symmetric 88 * key). For an example of such an implementation, see {@link LocalKeyInfoCredentialResolver}</li> 89 * <li>Finally, if no credentials have been otherwise resolved, a final post-processing hook is called: 90 * {@link #postProcessEmptyCredentials(KeyInfoResolutionContext, CriteriaSet, List)}. The default implementation is a 91 * no-op. This is an extension point by which subclasses may implement custom logic to resolve credentials in an 92 * implementation or context-specific manner, if no other mechanism has succeeded. Example usages might be to return a 93 * default set of credentials, or to use non-KeyInfo-derived criteria or contextual information to determine the 94 * credential or credentials to return.</li> 95 * </ol> 96 * </p> 97 * 98 */ 99 public class BasicProviderKeyInfoCredentialResolver extends AbstractCriteriaFilteringCredentialResolver implements 100 KeyInfoCredentialResolver { 101 102 /** Class logger. */ 103 private final Logger log = LoggerFactory.getLogger(BasicProviderKeyInfoCredentialResolver.class); 104 105 /** List of KeyInfo providers that are registered on this instance. */ 106 private List<KeyInfoProvider> providers; 107 108 /** 109 * Constructor. 110 * 111 * @param keyInfoProviders the list of KeyInfoProvider's to use in this resolver 112 */ 113 public BasicProviderKeyInfoCredentialResolver(List<KeyInfoProvider> keyInfoProviders) { 114 super(); 115 116 providers = new ArrayList<KeyInfoProvider>(); 117 providers.addAll(keyInfoProviders); 118 } 119 120 /** 121 * Return the list of the KeyInfoProvider instances used in this resolver configuration. 122 * 123 * @return the list of providers configured for this resolver instance 124 */ 125 protected List<KeyInfoProvider> getProviders() { 126 return providers; 127 } 128 129 /** {@inheritDoc} */ 130 protected Iterable<Credential> resolveFromSource(CriteriaSet criteriaSet) throws SecurityException { 131 KeyInfoCriteria kiCriteria = criteriaSet.get(KeyInfoCriteria.class); 132 if (kiCriteria == null) { 133 log.error("No KeyInfo criteria supplied, resolver could not process"); 134 throw new SecurityException("Credential criteria set did not contain an instance of" 135 + "KeyInfoCredentialCriteria"); 136 } 137 KeyInfo keyInfo = kiCriteria.getKeyInfo(); 138 139 // This will be the list of credentials to return. 140 List<Credential> credentials = new ArrayList<Credential>(); 141 142 KeyInfoResolutionContext kiContext = new KeyInfoResolutionContext(credentials); 143 144 // Note: we allow KeyInfo to be null to handle case where application context, 145 // other accompanying criteria, etc, should be used to resolve credentials via hooks below. 146 if (keyInfo != null) { 147 processKeyInfo(keyInfo, kiContext, criteriaSet, credentials); 148 } else { 149 log.info("KeyInfo was null, any credentials will be resolved by post-processing hooks only"); 150 } 151 152 // Postprocessing hook 153 postProcess(kiContext, criteriaSet, credentials); 154 155 // Final empty credential hook 156 if (credentials.isEmpty()) { 157 log.debug("No credentials were found, calling empty credentials post-processing hook"); 158 postProcessEmptyCredentials(kiContext, criteriaSet, credentials); 159 } 160 161 log.debug("A total of {} credentials were resolved", credentials.size()); 162 return credentials; 163 } 164 165 /** 166 * The main processing logic implemented by this resolver. 167 * 168 * @param keyInfo the KeyInfo being processed 169 * @param kiContext KeyInfo resolution context 170 * @param criteriaSet the credential criteria used to resolve credentials 171 * @param credentials the list which will store the resolved credentials 172 * @throws SecurityException thrown if there is an error during processing 173 */ 174 private void processKeyInfo(KeyInfo keyInfo, KeyInfoResolutionContext kiContext, CriteriaSet criteriaSet, 175 List<Credential> credentials) throws SecurityException { 176 177 // Initialize the resolution context that will be used by the provider plugins. 178 // This processes the KeyName and the KeyValue children, if either are present. 179 initResolutionContext(kiContext, keyInfo, criteriaSet); 180 181 // Store these off so we later use the original values, 182 // unmodified by other providers which later run. 183 Key keyValueKey = kiContext.getKey(); 184 HashSet<String> keyNames = new HashSet<String>(); 185 keyNames.addAll(kiContext.getKeyNames()); 186 187 // Now process all (non-KeyValue) children 188 processKeyInfoChildren(kiContext, criteriaSet, credentials); 189 190 if (credentials.isEmpty() && keyValueKey != null) { 191 // Add the credential based on plain KeyValue if no more specifc cred type was found 192 Credential keyValueCredential = buildBasicCredential(keyValueKey, keyNames); 193 if (keyValueCredential != null) { 194 log.debug("No credentials were extracted by registered non-KeyValue handling providers, " 195 + "adding KeyValue credential to returned credential set"); 196 credentials.add(keyValueCredential); 197 } 198 } 199 } 200 201 /** 202 * Hook for subclasses to do post-processing of the credential set after all KeyInfo children have been processed. 203 * 204 * For example, the previously resolved credentials might be used to index into a store of local credentials, where 205 * the index is a key name or the public half of a key pair extracted from the KeyInfo. 206 * 207 * @param kiContext KeyInfo resolution context 208 * @param criteriaSet the credential criteria used to resolve credentials 209 * @param credentials the list which will store the resolved credentials 210 * @throws SecurityException thrown if there is an error during processing 211 */ 212 protected void postProcess(KeyInfoResolutionContext kiContext, CriteriaSet criteriaSet, List<Credential> credentials) 213 throws SecurityException { 214 215 } 216 217 /** 218 * Hook for processing the case where no credentials were returned by any resolution method by any provider, nor by 219 * the processing of the {@link #postProcess(KeyInfoResolutionContext, CriteriaSet, List)} hook. 220 * 221 * @param kiContext KeyInfo resolution context 222 * @param criteriaSet the credential criteria used to resolve credentials 223 * @param credentials the list which will store the resolved credentials 224 * 225 * @throws SecurityException thrown if there is an error during processing 226 */ 227 protected void postProcessEmptyCredentials(KeyInfoResolutionContext kiContext, CriteriaSet criteriaSet, 228 List<Credential> credentials) throws SecurityException { 229 230 } 231 232 /** 233 * Use registered providers to process the non-KeyValue children of KeyInfo. 234 * 235 * Each child element is processed in document order. Each child element is processed by each provider in the 236 * ordered list of providers. The credential or credentials resolved by the first provider to successfully do so are 237 * added to the effective set resolved by the KeyInfo resolver. 238 * 239 * @param kiContext KeyInfo resolution context 240 * @param criteriaSet the credential criteria used to resolve credentials 241 * @param credentials the list which will store the resolved credentials 242 * @throws SecurityException thrown if there is a provider error processing the KeyInfo children 243 */ 244 protected void processKeyInfoChildren(KeyInfoResolutionContext kiContext, CriteriaSet criteriaSet, 245 List<Credential> credentials) throws SecurityException { 246 247 for (XMLObject keyInfoChild : kiContext.getKeyInfo().getXMLObjects()) { 248 249 if (keyInfoChild instanceof KeyValue) { 250 continue; 251 } 252 253 log.debug("Processing KeyInfo child with qname: {}", keyInfoChild.getElementQName()); 254 Collection<Credential> childCreds = processKeyInfoChild(kiContext, criteriaSet, keyInfoChild); 255 256 if (childCreds != null && !childCreds.isEmpty()) { 257 credentials.addAll(childCreds); 258 } else { 259 // Not really an error or warning if KeyName doesn't produce a credential 260 if (keyInfoChild instanceof KeyName) { 261 log.debug("KeyName, with value {}, did not independently produce a credential based on any registered providers", 262 ((KeyName) keyInfoChild).getValue()); 263 264 } else { 265 log.warn("No credentials could be extracted from KeyInfo child with qname {} by any registered provider", 266 keyInfoChild.getElementQName()); 267 } 268 } 269 } 270 } 271 272 /** 273 * Process the given KeyInfo child with the registered providers. 274 * 275 * The child element is processed by each provider in the ordered list of providers. The credential or credentials 276 * resolved by the first provider to successfully do so are returned and processing of the child element is 277 * terminated. 278 * 279 * @param kiContext KeyInfo resolution context 280 * @param criteriaSet the credential criteria used to resolve credentials 281 * @param keyInfoChild the KeyInfo to evaluate 282 * @return the collection of resolved credentials, or null 283 * @throws SecurityException thrown if there is a provider error processing the KeyInfo child 284 */ 285 protected Collection<Credential> processKeyInfoChild(KeyInfoResolutionContext kiContext, CriteriaSet criteriaSet, 286 XMLObject keyInfoChild) throws SecurityException { 287 288 for (KeyInfoProvider provider : getProviders()) { 289 290 if (!provider.handles(keyInfoChild)) { 291 log.debug("Provider {} doesn't handle objects of type {}, skipping", provider.getClass().getName(), 292 keyInfoChild.getElementQName()); 293 continue; 294 } 295 296 log.debug("Processing KeyInfo child {} with provider {}", keyInfoChild.getElementQName(), provider 297 .getClass().getName()); 298 Collection<Credential> creds = provider.process(this, keyInfoChild, criteriaSet, kiContext); 299 300 if (creds != null && !creds.isEmpty()) { 301 log.debug("Credentials successfully extracted from child {} by provider {}", keyInfoChild 302 .getElementQName(), provider.getClass().getName()); 303 return creds; 304 } 305 } 306 return null; 307 } 308 309 /** 310 * Initialize the resolution context that will be used by the providers. 311 * 312 * The supplied KeyInfo object is stored in the context, as well as the values of any {@link KeyName} children 313 * present. Finally if a credential is resolveble by any registered provider from a plain {@link KeyValue} child, 314 * the key from that credential is also stored in the context. 315 * 316 * @param kiContext KeyInfo resolution context 317 * @param keyInfo the KeyInfo to evaluate 318 * @param criteriaSet the credential criteria used to resolve credentials 319 * @throws SecurityException thrown if there is an error processing the KeyValue children 320 */ 321 protected void initResolutionContext(KeyInfoResolutionContext kiContext, KeyInfo keyInfo, CriteriaSet criteriaSet) 322 throws SecurityException { 323 324 kiContext.setKeyInfo(keyInfo); 325 326 // Extract all KeyNames 327 kiContext.getKeyNames().addAll(KeyInfoHelper.getKeyNames(keyInfo)); 328 log.debug("Found {} key names: {}", kiContext.getKeyNames().size(), kiContext.getKeyNames()); 329 330 // Extract the Credential based on the (singular) key from an existing KeyValue(s). 331 resolveKeyValue(kiContext, criteriaSet, keyInfo.getKeyValues()); 332 } 333 334 /** 335 * Resolve the key from any KeyValue element that may be present, and store the resulting key in the resolution 336 * context. 337 * 338 * Each KeyValue element is processed in turn in document order. Each Keyvalue will be processed by each provider in 339 * the ordered list of registered providers. The key from the first credential successfully resolved from a KeyValue 340 * will be stored in the resolution context. 341 * 342 * Note: This resolver implementation assumes that KeyInfo/KeyValue will not be abused via-a-vis the Signature 343 * specificiation, and that therefore all KeyValue elements (if there is even more than one) will all resolve to the 344 * same key value. The KeyInfo might, for example have multiple KeyValue children, containing different 345 * representations of the same key. Therefore, only the first credential derived from a KeyValue will be be 346 * utilized. 347 * 348 * @param kiContext KeyInfo resolution context 349 * @param criteriaSet the credential criteria used to resolve credentials 350 * @param keyValues the KeyValue children to evaluate 351 * @throws SecurityException thrown if there is an error resolving the key from the KeyValue 352 */ 353 protected void resolveKeyValue(KeyInfoResolutionContext kiContext, CriteriaSet criteriaSet, List<KeyValue> keyValues) 354 throws SecurityException { 355 356 for (KeyValue keyValue : keyValues) { 357 Collection<Credential> creds = processKeyInfoChild(kiContext, criteriaSet, keyValue); 358 if (creds != null) { 359 for (Credential cred : creds) { 360 Key key = extractKeyValue(cred); 361 if (key != null) { 362 kiContext.setKey(key); 363 log.debug("Found a credential based on a KeyValue having key type: {}", key.getAlgorithm()); 364 return; 365 } 366 } 367 } 368 } 369 } 370 371 /** 372 * Construct a basic credential containing the specified key and set of key names. 373 * 374 * @param key the key to include in the credential 375 * @param keyNames the key names to include in the credential 376 * @return a basic credential with the specified key and key names 377 * @throws SecurityException if there is an error building the credential 378 */ 379 protected Credential buildBasicCredential(Key key, Set<String> keyNames) throws SecurityException { 380 if (key == null) { 381 log.debug("Key supplied was null, could not build credential"); 382 return null; 383 } 384 385 BasicCredential basicCred = new BasicCredential(); 386 387 basicCred.getKeyNames().addAll(keyNames); 388 389 if (key instanceof PublicKey) { 390 basicCred.setPublicKey((PublicKey) key); 391 } else if (key instanceof SecretKey) { 392 basicCred.setSecretKey((SecretKey) key); 393 } else if (key instanceof PrivateKey) { 394 // This would be unusual for most KeyInfo use cases, 395 // but go ahead and try and handle it 396 PrivateKey privateKey = (PrivateKey) key; 397 try { 398 PublicKey publicKey = SecurityHelper.derivePublicKey(privateKey); 399 if (publicKey != null) { 400 basicCred.setPublicKey(publicKey); 401 basicCred.setPrivateKey(privateKey); 402 } else { 403 log.error("Failed to derive public key from private key"); 404 return null; 405 } 406 } catch (KeyException e) { 407 log.error("Could not derive public key from private key", e); 408 return null; 409 } 410 } else { 411 log.error(String.format("Key was of an unsupported type '%s'", key.getClass().getName())); 412 return null; 413 } 414 415 return basicCred; 416 } 417 418 /** 419 * Utility method to extract any key that might be present in the specified Credential. 420 * 421 * @param cred the Credential to evaluate 422 * @return the Key contained in the credential, or null if it does not contain a key. 423 */ 424 protected Key extractKeyValue(Credential cred) { 425 if (cred == null) { 426 return null; 427 } 428 if (cred.getPublicKey() != null) { 429 return cred.getPublicKey(); 430 } 431 // This could happen if key is derived, e.g. key agreement, etc 432 if (cred.getSecretKey() != null) { 433 return cred.getSecretKey(); 434 } 435 // Perhaps unlikely, but go ahead and check 436 if (cred.getPrivateKey() != null) { 437 return cred.getPrivateKey(); 438 } 439 return null; 440 } 441 442 }