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 }