View Javadoc

1   /*
2    * Copyright [2006] [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.encryption;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.InputStream;
21  import java.security.Key;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.apache.xml.security.Init;
30  import org.apache.xml.security.encryption.XMLCipher;
31  import org.apache.xml.security.encryption.XMLEncryptionException;
32  import org.opensaml.xml.Configuration;
33  import org.opensaml.xml.XMLObject;
34  import org.opensaml.xml.io.Marshaller;
35  import org.opensaml.xml.io.MarshallingException;
36  import org.opensaml.xml.io.UnmarshallerFactory;
37  import org.opensaml.xml.io.UnmarshallingException;
38  import org.opensaml.xml.parse.BasicParserPool;
39  import org.opensaml.xml.parse.XMLParserException;
40  import org.opensaml.xml.security.Criteria;
41  import org.opensaml.xml.security.CriteriaSet;
42  import org.opensaml.xml.security.SecurityException;
43  import org.opensaml.xml.security.SecurityHelper;
44  import org.opensaml.xml.security.credential.Credential;
45  import org.opensaml.xml.security.credential.UsageType;
46  import org.opensaml.xml.security.criteria.KeyAlgorithmCriteria;
47  import org.opensaml.xml.security.criteria.KeyLengthCriteria;
48  import org.opensaml.xml.security.criteria.UsageCriteria;
49  import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
50  import org.opensaml.xml.security.keyinfo.KeyInfoCriteria;
51  import org.opensaml.xml.signature.DigestMethod;
52  import org.opensaml.xml.signature.SignatureConstants;
53  import org.opensaml.xml.util.DatatypeHelper;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  import org.w3c.dom.Document;
57  import org.w3c.dom.DocumentFragment;
58  import org.w3c.dom.Element;
59  import org.w3c.dom.Node;
60  import org.w3c.dom.NodeList;
61  
62  /**
63   * Supports decryption of XMLObjects which represent data encrypted according to the XML Encryption specification,
64   * version 20021210.
65   * 
66   * <p>
67   * Details on the components specified as constructor options are as follows:
68   * <ol>
69   * 
70   * <li>
71   * <code>newResolver</code>: This {@link KeyInfoCredentialResolver} instance is used to resolve keys (as Credentials)
72   * based on the KeyInfo of EncryptedData elements. While it could in theory be used to handle the complete process of 
73   * resolving the data decryption key, including decrypting any necessary EncryptedKey's, it would typically
74   * be used in cases where encrypted key transport via an EncryptedKey is not being employed. 
75   * This corresponds to scenarios where decryption is instead based on resolving the (presumably shared secret)
76   * symmetric data decryption key directly, based on either context or information present in the 
77   * EncryptedData's KeyInfo. In cases where the data decryption key is to be resolved by decrypting an EncryptedKey,
78   * this resolver would typically not be used and may be <code>null</code>.
79   * </li>
80   * 
81   * <li>
82   * <code>newKEKResolver</code>: This {@link KeyInfoCredentialResolver} instance is used to resolve keys (as Credentials)
83   * used to decrypt EncryptedKey elements, based on the KeyInfo information contained within the EncryptedKey element 
84   * (also known as a Key Encryption Key or KEK). For asymmetric key transport of encrypted keys, this would entail 
85   * resolving the private key which corresponds to the public key which was used to encrypt the EncryptedKey.
86   * </li>
87   * 
88   * <li>
89   * <code>newEncKeyResolver</code>: This {@link EncryptedKeyResolver} instance is responsible for resolving
90   * the EncryptedKey element(s) which hold(s) the encrypted data decryption key which would be used to
91   * decrypt an EncryptedData element. 
92   * </li>
93   * 
94   * </ol>
95   * </p>
96   * 
97   * <p>
98   * XML Encryption can encrypt either a single {@link Element} or the contents of an Element. The caller of this class
99   * must select the decryption method which is most appropriate for their specific use case.
100  * </p>
101  * 
102  * <p>
103  * Note that the type of plaintext data contained by an {@link EncryptedData} can be checked prior to decryption by
104  * examining that element's <code>type</code> attribute ({@link EncryptedData#getType}). This (optional) attribute
105  * may contain one of the following two constant values to aid in the decryption process:
106  * {@link EncryptionConstants#TYPE_ELEMENT} or {@link EncryptionConstants#TYPE_CONTENT}.
107  * </p>
108  * 
109  * <p>
110  * By nature the fundamental output of XML decryption is a DOM {@link DocumentFragment} with 1 or more immediate
111  * top-level DOM {@link Node} children. This case is reflected in the method {@link #decryptDataToDOM(EncryptedData)}.
112  * It is up to the caller to properly process the DOM Nodes which are the children of this document fragment. The
113  * DocumentFragment and its Node children will be owned by the DOM {@link Document} which owned the original
114  * EncryptedData before decryption. Note, however, that the Node children will not be a part of the tree of Nodes rooted
115  * at that Document's document element.
116  * </p>
117  * 
118  * <p>
119  * A typical use case will be that the content which was encrypted contained solely {@link Element} nodes. For this use
120  * case a convenience method is provided as {@link #decryptDataToList(EncryptedData)}, which returns a list of
121  * {@link XMLObject}'s which are the result of unmarshalling each of the child Elements of the decrypted
122  * DocumentFragment.
123  * </p>
124  * 
125  * <p>
126  * Another typical use case is that the content which was encrypted was a single Element. For this use case a
127  * convenience method is provided as {@link #decryptData(EncryptedData)}, which returns a single XMLObject which was
128  * the result of unmarshalling this decrypted Element.
129  * </p>
130  * 
131  * <p>
132  * In both of these cases the underlying DOM Element which is represented by each of the returned XMLObjects will be
133  * owned by the DOM Document which also owns the original EncrytpedData Element. However, note that these cached DOM
134  * Elements are <strong>not</strong> part of the tree of Nodes rooted at that Document's document element. If these
135  * returned XMLObjects are then inserted as the children of other XMLObjects, it is up to the caller to ensure that the
136  * XMLObject tree is then remarshalled if the relationship of the cached DOM nodes is important (e.g. resolution of
137  * ID-typed attributes via {@link Document#getElementById(String)}).
138  * </p>
139  * 
140  * <p>
141  * For some use cases where the returned XMLObjects will not necessarily be stored back as children of another parent
142  * XMLObject, it may still necessary for the DOM Elements of the resultant XMLObjects to exist within the tree of Nodes
143  * rooted at a DOM Document's document element (e.g. signature verification on the standalone decrypted XMLObject). For
144  * these cases these method variants may be used: {@link #decryptDataToList(EncryptedData, boolean)} and
145  * {@link #decryptData(EncryptedData, boolean)}.  The <code>rootInNewDocument</code> parameter is explained below.
146  * A default value for this parameter, for the overloaded convenience methods
147  * which do not take this parameter explicitly, may be set via {@link #setRootInNewDocument(boolean)}.
148  * This default value is initialized to <code>false</code>.
149  * </p>
150  * 
151  * <p>If the boolean option <code>rootInNewDocument</code> is true at the time of decryption,
152  * then for each top-level child Element of the decrypted DocumentFragment, the following will occur:
153  * 
154  * <ol>
155  * <li>A new DOM Document will be created.</li>
156  * <li>The Element will be adopted into that Document.</li>
157  * <li>The Element will be made the root element of the Document.</li>
158  * <li>The Element will be unmarshalled into an XMLObject as in the single argument variant.</li>
159  * </ol>
160  * 
161  * <p>
162  * Note that new Document creation, node adoption and rooting the new document element are potentially very expensive.
163  * This should only be done where the caller's use case really requires it.
164  * </p>
165  * 
166  */
167 public class Decrypter {
168 
169     /** ParserPool used in parsing decrypted data. */
170     private final BasicParserPool parserPool;
171 
172     /** Unmarshaller factory, used in decryption of EncryptedData objects. */
173     private UnmarshallerFactory unmarshallerFactory;
174 
175     /** Load-and-Save DOM Implementation singleton. */
176     // private DOMImplementationLS domImplLS;
177     /** Class logger. */
178     private final Logger log = LoggerFactory.getLogger(Decrypter.class);
179 
180     /** Resolver for data encryption keys. */
181     private KeyInfoCredentialResolver resolver;
182 
183     /** Resolver for key encryption keys. */
184     private KeyInfoCredentialResolver kekResolver;
185 
186     /** Resolver for EncryptedKey instances which contain the encrypted data encryption key. */
187     private EncryptedKeyResolver encKeyResolver;
188 
189     /** Additional criteria to use when resolving credentials based on an EncryptedData's KeyInfo. */
190     private CriteriaSet resolverCriteria;
191 
192     /** Additional criteria to use when resolving credentials based on an EncryptedKey's KeyInfo. */
193     private CriteriaSet kekResolverCriteria;
194 
195     /** The name of the JCA security provider to use. */
196     private String jcaProviderName;
197     
198     /** Flag to determine whether by default the Element which backs the underlying decrypted SAMLObject will be the 
199      * root of a new DOM document. */
200     private boolean defaultRootInNewDocument;
201     
202 
203     /**
204      * Constructor.
205      * 
206      * @param newResolver resolver for data encryption keys.
207      * @param newKEKResolver resolver for key encryption keys.
208      * @param newEncKeyResolver resolver for EncryptedKey elements
209      */
210     public Decrypter(KeyInfoCredentialResolver newResolver, KeyInfoCredentialResolver newKEKResolver,
211             EncryptedKeyResolver newEncKeyResolver) {
212         resolver = newResolver;
213         kekResolver = newKEKResolver;
214         encKeyResolver = newEncKeyResolver;
215 
216         resolverCriteria = null;
217         kekResolverCriteria = null;
218 
219         // Note: this is hopefully only temporary, until Xerces implements DOM 3 LSParser.parseWithContext().
220         parserPool = new BasicParserPool();
221         parserPool.setNamespaceAware(true);
222 
223         // Note: this is necessary due to an unresolved Xerces deferred DOM issue/bug
224         HashMap<String, Boolean> features = new HashMap<String, Boolean>();
225         features.put("http://apache.org/xml/features/dom/defer-node-expansion", Boolean.FALSE);
226         parserPool.setBuilderFeatures(features);
227 
228         unmarshallerFactory = Configuration.getUnmarshallerFactory();
229         
230         defaultRootInNewDocument = false;
231     }
232     
233     /**
234      * Get the flag which indicates whether by default the DOM Element which backs a decrypted SAML object
235      * will be the root of a new DOM document.  Defaults to false.
236      * 
237      * @return the current value of the flag for this decrypter instance
238      */
239     public boolean isRootInNewDocument() {
240         return defaultRootInNewDocument;
241     }
242     
243     /**
244      * Set the flag which indicates whether by default the DOM Element which backs a decrypted SAML object
245      * will be the root of a new DOM document.  Defaults to false.
246      * 
247      * @param flag the current value of the flag for this decrypter instance
248      */
249     public void setRootInNewDocument(boolean flag) {
250        defaultRootInNewDocument = flag; 
251     }
252 
253     /**
254      * Get the Java Cryptography Architecture (JCA) security provider name that should be used to provide the decryption
255      * support.
256      * 
257      * Defaults to <code>null</code>, which means that the first registered provider which supports the indicated
258      * encryption algorithm URI will be used.
259      * 
260      * @return the JCA provider name to use
261      */
262     public String getJCAProviderName() {
263         return jcaProviderName;
264     }
265 
266     /**
267      * Set the Java Cryptography Architecture (JCA) security provider name that should be used to provide the decryption
268      * support.
269      * 
270      * Defaults to <code>null</code>, which means that the first registered provider which supports the indicated
271      * encryption algorithm URI will be used.
272      * 
273      * @param providerName the JCA provider name to use
274      */
275     public void setJCAProviderName(String providerName) {
276         jcaProviderName = providerName;
277     }
278 
279     /**
280      * Get the data encryption key credential resolver.
281      * 
282      * @return the data encryption key resolver
283      */
284     public KeyInfoCredentialResolver getKeyResolver() {
285         return resolver;
286     }
287 
288     /**
289      * Set a new data encryption key credential resolver.
290      * 
291      * @param newResolver the new data encryption key resolver
292      */
293     public void setKeyResolver(KeyInfoCredentialResolver newResolver) {
294         resolver = newResolver;
295     }
296 
297     /**
298      * Get the key encryption key credential resolver.
299      * 
300      * @return the key encryption key resolver
301      */
302     public KeyInfoCredentialResolver getKEKResolver() {
303         return kekResolver;
304     }
305 
306     /**
307      * Set a new key encryption key credential resolver.
308      * 
309      * @param newKEKResolver the new key encryption key resolver
310      */
311     public void setKEKResolver(KeyInfoCredentialResolver newKEKResolver) {
312         kekResolver = newKEKResolver;
313     }
314 
315     /**
316      * Get the encrypted key resolver.
317      * 
318      * @return the encrypted key resolver
319      */
320     public EncryptedKeyResolver getEncryptedKeyResolver() {
321         return encKeyResolver;
322     }
323 
324     /**
325      * Set a new encrypted key resolver.
326      * 
327      * @param newResolver the new encrypted key resolver
328      */
329     public void setEncryptedKeyResolver(EncryptedKeyResolver newResolver) {
330         encKeyResolver = newResolver;
331     }
332 
333     /**
334      * Get the optional static set of criteria used when resolving credentials based on the KeyInfo of an EncryptedData
335      * element.
336      * 
337      * @return the static criteria set to use
338      */
339     public CriteriaSet setKeyResolverCriteria() {
340         return resolverCriteria;
341     }
342 
343     /**
344      * Set the optional static set of criteria used when resolving credentials based on the KeyInfo of an EncryptedData
345      * element.
346      * 
347      * @param newCriteria the static criteria set to use
348      */
349     public void setKeyResolverCriteria(CriteriaSet newCriteria) {
350         resolverCriteria = newCriteria;
351     }
352 
353     /**
354      * Get the optional static set of criteria used when resolving credentials based on the KeyInfo of an EncryptedKey
355      * element.
356      * 
357      * @return the static criteria set to use
358      */
359     public CriteriaSet getKEKResolverCriteria() {
360         return kekResolverCriteria;
361     }
362 
363     /**
364      * Set the optional static set of criteria used when resolving credentials based on the KeyInfo of an EncryptedKey
365      * element.
366      * 
367      * @param newCriteria the static criteria set to use
368      */
369     public void setKEKResolverCriteria(CriteriaSet newCriteria) {
370         kekResolverCriteria = newCriteria;
371     }
372 
373     /**
374      * This is a convenience method for calling {@link #decryptData(EncryptedData, boolean)},
375      * with the <code>rootInNewDocument</code> parameter value supplied by {@link #isRootInNewDocument()}.
376      * 
377      * @param encryptedData encrypted data element containing the data to be decrypted
378      * @return the decrypted XMLObject
379      * @throws DecryptionException exception indicating a decryption error, possibly because the decrypted data
380      *             contained more than one top-level Element, or some non-Element Node type.
381      */
382     public XMLObject decryptData(EncryptedData encryptedData) throws DecryptionException {
383         return decryptData(encryptedData, isRootInNewDocument());
384     }
385 
386     /**
387      * Decrypts the supplied EncryptedData and returns the resulting XMLObject.
388      * 
389      * This will only succeed if the decrypted EncryptedData contains exactly one DOM Node of type Element.
390      * 
391      * @param encryptedData encrypted data element containing the data to be decrypted
392      * @param rootInNewDocument if true, root the underlying Element of the returned XMLObject in a new Document as
393      *            described in {@link Decrypter}
394      * @return the decrypted XMLObject
395      * @throws DecryptionException exception indicating a decryption error, possibly because the decrypted data
396      *             contained more than one top-level Element, or some non-Element Node type.
397      */
398     public XMLObject decryptData(EncryptedData encryptedData, boolean rootInNewDocument) throws DecryptionException {
399 
400         List<XMLObject> xmlObjects = decryptDataToList(encryptedData, rootInNewDocument);
401         if (xmlObjects.size() != 1) {
402             log.error("The decrypted data contained more than one top-level XMLObject child");
403             throw new DecryptionException("The decrypted data contained more than one XMLObject child");
404         }
405 
406         return xmlObjects.get(0);
407     }
408 
409     /**
410      * This is a convenience method for calling {@link #decryptDataToList(EncryptedData, boolean)},
411      * with the <code>rootInNewDocument</code> parameter value supplied by {@link #isRootInNewDocument()}.
412      * 
413      * @param encryptedData encrypted data element containing the data to be decrypted
414      * @return the list decrypted top-level XMLObjects
415      * @throws DecryptionException exception indicating a decryption error, possibly because the decrypted data
416      *             contained DOM nodes other than type of Element
417      */
418     public List<XMLObject> decryptDataToList(EncryptedData encryptedData) throws DecryptionException {
419         return decryptDataToList(encryptedData, isRootInNewDocument());
420     }
421 
422     /**
423      * Decrypts the supplied EncryptedData and returns the resulting list of XMLObjects.
424      * 
425      * This will succeed only if the decrypted EncryptedData contains at the top-level only DOM Elements (not other
426      * types of DOM Nodes).
427      * 
428      * @param encryptedData encrypted data element containing the data to be decrypted
429      * @param rootInNewDocument if true, root the underlying Elements of the returned XMLObjects in a new Document as
430      *            described in {@link Decrypter}
431      * @return the list decrypted top-level XMLObjects
432      * @throws DecryptionException exception indicating a decryption error, possibly because the decrypted data
433      *             contained DOM nodes other than type of Element
434      */
435     public List<XMLObject> decryptDataToList(EncryptedData encryptedData, boolean rootInNewDocument)
436             throws DecryptionException {
437         List<XMLObject> xmlObjects = new LinkedList<XMLObject>();
438 
439         DocumentFragment docFragment = decryptDataToDOM(encryptedData);
440 
441         XMLObject xmlObject;
442         Node node;
443         Element element;
444 
445         NodeList children = docFragment.getChildNodes();
446         for (int i = 0; i < children.getLength(); i++) {
447             node = children.item(i);
448             if (node.getNodeType() != Node.ELEMENT_NODE) {
449                 log.error("Decryption returned a top-level node that was not of type Element: " + node.getNodeType());
450                 throw new DecryptionException("Top-level node was not of type Element");
451             } else {
452                 element = (Element) node;
453                 if (rootInNewDocument) {
454                     Document newDoc = null;
455                     try {
456                         newDoc = parserPool.newDocument();
457                     } catch (XMLParserException e) {
458                         log.error("There was an error creating a new DOM Document", e);
459                         throw new DecryptionException("Error creating new DOM Document", e);
460                     }
461                     newDoc.adoptNode(element);
462                     newDoc.appendChild(element);
463                 }
464             }
465 
466             try {
467                 xmlObject = unmarshallerFactory.getUnmarshaller(element).unmarshall(element);
468             } catch (UnmarshallingException e) {
469                 log.error("There was an error during unmarshalling of the decrypted element", e);
470                 throw new DecryptionException("Unmarshalling error during decryption", e);
471             }
472 
473             xmlObjects.add(xmlObject);
474         }
475 
476         return xmlObjects;
477     }
478 
479     /**
480      * Decrypts the supplied EncryptedData and returns the resulting DOM {@link DocumentFragment}.
481      * 
482      * @param encryptedData encrypted data element containing the data to be decrypted
483      * @return the decrypted DOM {@link DocumentFragment}
484      * @throws DecryptionException exception indicating a decryption error
485      */
486     public DocumentFragment decryptDataToDOM(EncryptedData encryptedData) throws DecryptionException {
487         if (resolver == null && encKeyResolver == null) {
488             log.error("Decryption can not be attempted, required resolvers are not available");
489             throw new DecryptionException("Unable to decrypt EncryptedData, required resolvers are not available");
490         }
491 
492         DocumentFragment docFrag = null;
493 
494         if (resolver != null) {
495             docFrag = decryptUsingResolvedKey(encryptedData);
496             if (docFrag != null) {
497                 return docFrag;
498             } else {
499                 log.debug("Failed to decrypt EncryptedData using standard KeyInfo resolver");
500             }
501         }
502 
503         String algorithm = encryptedData.getEncryptionMethod().getAlgorithm();
504         if (DatatypeHelper.isEmpty(algorithm)) {
505             String msg = "EncryptedData's EncryptionMethod Algorithm attribute was empty, "
506                 + "key decryption could not be attempted";
507             log.error(msg);
508             throw new DecryptionException(msg);
509         }
510 
511         if (encKeyResolver != null) {
512             docFrag = decryptUsingResolvedEncryptedKey(encryptedData, algorithm);
513             if (docFrag != null) {
514                 return docFrag;
515             } else {
516                 log.debug("Failed to decrypt EncryptedData using EncryptedKeyResolver");
517             }
518         }
519 
520         log.error("Failed to decrypt EncryptedData using either EncryptedData KeyInfoCredentialResolver "
521                 + "or EncryptedKeyResolver + EncryptedKey KeyInfoCredentialResolver");
522         
523         throw new DecryptionException("Failed to decrypt EncryptedData");
524     }
525 
526     /**
527      * Decrypts the supplied EncryptedData using the specified key, and returns the resulting DOM
528      * {@link DocumentFragment}.
529      * 
530      * @param encryptedData encrypted data element containing the data to be decrypted
531      * @param dataEncKey Java Key with which to attempt decryption of the encrypted data
532      * @return the decrypted DOM {@link DocumentFragment}
533      * @throws DecryptionException exception indicating a decryption error
534      */
535     public DocumentFragment decryptDataToDOM(EncryptedData encryptedData, Key dataEncKey) throws DecryptionException {
536 
537         // TODO Until Xerces supports LSParser.parseWithContext(), or we come up with another solution
538         // to parse a bytestream into a DocumentFragment, we can only support encryption of type
539         // Element (i.e. a single Element), not content.
540         if (!EncryptionConstants.TYPE_ELEMENT.equals(encryptedData.getType())) {
541             log.error("EncryptedData was of unsupported type '" + encryptedData.getType()
542                     + "', could not attempt decryption");
543             throw new DecryptionException("EncryptedData of unsupported type was encountered");
544         }
545         if (dataEncKey == null) {
546             log.error("Data decryption key was null");
547             throw new IllegalArgumentException("Data decryption key may not be null");
548         }
549 
550         try {
551             checkAndMarshall(encryptedData);
552         } catch (DecryptionException e) {
553             log.error("Error marshalling EncryptedData for decryption", e);
554             throw e;
555         }
556         Element targetElement = encryptedData.getDOM();
557 
558         XMLCipher xmlCipher;
559         try {
560             if (getJCAProviderName() != null) {
561                 xmlCipher = XMLCipher.getProviderInstance(getJCAProviderName());
562             } else {
563                 xmlCipher = XMLCipher.getInstance();
564             }
565             xmlCipher.init(XMLCipher.DECRYPT_MODE, dataEncKey);
566         } catch (XMLEncryptionException e) {
567             log.error("Error initialzing cipher instance on data decryption", e);
568             throw new DecryptionException("Error initialzing cipher instance on data decryption", e);
569         }
570 
571         byte[] bytes = null;
572         try {
573             bytes = xmlCipher.decryptToByteArray(targetElement);
574         } catch (XMLEncryptionException e) {
575             log.error("Error decrypting the encrypted data element", e);
576             throw new DecryptionException("Error decrypting the encrypted data element", e);
577         }
578         if (bytes == null) {
579             throw new DecryptionException("EncryptedData could not be decrypted");
580         }
581         ByteArrayInputStream input = new ByteArrayInputStream(bytes);
582         DocumentFragment docFragment = parseInputStream(input, encryptedData.getDOM().getOwnerDocument());
583         return docFragment;
584     }
585 
586     /**
587      * Attempts to decrypt the supplied EncryptedKey and returns the resulting Java security Key object. The algorithm
588      * of the decrypted key must be supplied by the caller based on knowledge of the associated EncryptedData
589      * information.
590      * 
591      * @param encryptedKey encrypted key element containing the encrypted key to be decrypted
592      * @param algorithm the algorithm associated with the decrypted key
593      * @return the decrypted key
594      * @throws DecryptionException exception indicating a decryption error
595      */
596     public Key decryptKey(EncryptedKey encryptedKey, String algorithm) throws DecryptionException {
597         if (kekResolver == null) {
598             log.warn("No KEK KeyInfo credential resolver is available, can not attempt EncryptedKey decryption");
599             throw new DecryptionException("No KEK KeyInfo resolver is available for EncryptedKey decryption");
600         }
601 
602         if (DatatypeHelper.isEmpty(algorithm)) {
603             log.error("Algorithm of encrypted key not supplied, key decryption cannot proceed.");
604             throw new DecryptionException("Algorithm of encrypted key not supplied, key decryption cannot proceed.");
605         }
606 
607         CriteriaSet criteriaSet = buildCredentialCriteria(encryptedKey, kekResolverCriteria);
608         try {
609             for (Credential cred : kekResolver.resolve(criteriaSet)) {
610                 try {
611                     return decryptKey(encryptedKey, algorithm, SecurityHelper.extractDecryptionKey(cred));
612                 } catch (DecryptionException e) {
613                     String msg = "Attempt to decrypt EncryptedKey using credential from KEK KeyInfo resolver failed: ";
614                     log.debug(msg, e);
615                     continue;
616                 }
617             }
618         } catch (SecurityException e) {
619             log.error("Error resolving credentials from EncryptedKey KeyInfo", e);
620         }
621 
622         log.error("Failed to decrypt EncryptedKey, valid decryption key could not be resolved");
623         throw new DecryptionException("Valid decryption key for EncryptedKey could not be resolved");
624     }
625 
626     /**
627      * Decrypts the supplied EncryptedKey and returns the resulting Java security Key object. The algorithm of the
628      * decrypted key must be supplied by the caller based on knowledge of the associated EncryptedData information.
629      * 
630      * @param encryptedKey encrypted key element containing the encrypted key to be decrypted
631      * @param algorithm the algorithm associated with the decrypted key
632      * @param kek the key encryption key with which to attempt decryption of the encrypted key
633      * @return the decrypted key
634      * @throws DecryptionException exception indicating a decryption error
635      */
636     public Key decryptKey(EncryptedKey encryptedKey, String algorithm, Key kek) throws DecryptionException {
637         if (kek == null) {
638             log.error("Data encryption key was null");
639             throw new IllegalArgumentException("Data encryption key may not be null");
640         }
641         if (DatatypeHelper.isEmpty(algorithm)) {
642             log.error("Algorithm of encrypted key not supplied, key decryption cannot proceed.");
643             throw new DecryptionException("Algorithm of encrypted key not supplied, key decryption cannot proceed.");
644         }
645 
646         try {
647             checkAndMarshall(encryptedKey);
648         } catch (DecryptionException e) {
649             log.error("Error marshalling EncryptedKey for decryption", e);
650             throw e;
651         }
652         
653         preProcessEncryptedKey(encryptedKey, algorithm, kek);
654         
655         Element targetElement = encryptedKey.getDOM();
656 
657         XMLCipher xmlCipher;
658         try {
659             if (getJCAProviderName() != null) {
660                 xmlCipher = XMLCipher.getProviderInstance(getJCAProviderName());
661             } else {
662                 xmlCipher = XMLCipher.getInstance();
663             }
664             xmlCipher.init(XMLCipher.UNWRAP_MODE, kek);
665         } catch (XMLEncryptionException e) {
666             log.error("Error initialzing cipher instance on key decryption", e);
667             throw new DecryptionException("Error initialzing cipher instance on key decryption", e);
668         }
669 
670         org.apache.xml.security.encryption.EncryptedKey encKey;
671         try {
672             encKey = xmlCipher.loadEncryptedKey(targetElement.getOwnerDocument(), targetElement);
673         } catch (XMLEncryptionException e) {
674             log.error("Error when loading library native encrypted key representation", e);
675             throw new DecryptionException("Error when loading library native encrypted key representation", e);
676         }
677 
678         Key key = null;
679         try {
680             key = xmlCipher.decryptKey(encKey, algorithm);
681         } catch (XMLEncryptionException e) {
682             log.error("Error decrypting encrypted key", e);
683             throw new DecryptionException("Error decrypting encrypted key", e);
684         }
685         if (key == null) {
686             throw new DecryptionException("Key could not be decrypted");
687         }
688         return key;
689     }
690 
691     /**
692      * Preprocess the EncryptedKey. For example, check for supported algorithms.
693      * 
694      * @param encryptedKey encrypted key element containing the encrypted key to be decrypted
695      * @param algorithm the algorithm associated with the decrypted key
696      * @param kek the key encryption key with which to attempt decryption of the encrypted key
697      * 
698      * @throws DecryptionException exception indicating a decryption error
699      */
700     protected void preProcessEncryptedKey(EncryptedKey encryptedKey, String algorithm, Key kek) 
701             throws DecryptionException {
702         
703         // Apache XML-Security currently only supports an internal, hard-coded default
704         // SHA-1 digest method with RSA-OAEP key transport.
705         String keyTransportAlgorithm = encryptedKey.getEncryptionMethod().getAlgorithm();
706         if (EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP.equals(keyTransportAlgorithm)) {
707             List<XMLObject> digestMethods = 
708                 encryptedKey.getEncryptionMethod().getUnknownXMLObjects(DigestMethod.DEFAULT_ELEMENT_NAME);
709             if (!digestMethods.isEmpty()) {
710                 DigestMethod dm = (DigestMethod) digestMethods.get(0);
711                 if (! SignatureConstants.ALGO_ID_DIGEST_SHA1
712                         .equals(DatatypeHelper.safeTrimOrNullString(dm.getAlgorithm())) ) {
713                     log.error("EncryptedKey/EncryptionMethod/DigestMethod contains unsupported algorithm URI: {}",
714                             dm.getAlgorithm());
715                     throw new DecryptionException(
716                             "EncryptedKey/EncryptionMethod/DigestMethod contains unsupported algorithm URI");
717                 }
718             }
719         }
720         
721     }
722 
723     /**
724      * Attempt to decrypt by resolving the decryption key using the standard credential resolver.
725      * 
726      * @param encryptedData the encrypted data to decrypt
727      * @return the decrypted document fragment, or null if decryption key could not be resolved or decryption failed
728      */
729     private DocumentFragment decryptUsingResolvedKey(EncryptedData encryptedData) {
730         if (resolver != null) {
731             CriteriaSet criteriaSet = buildCredentialCriteria(encryptedData, resolverCriteria);
732             try {
733                 for (Credential cred : resolver.resolve(criteriaSet)) {
734                     try {
735                         return decryptDataToDOM(encryptedData, SecurityHelper.extractDecryptionKey(cred));
736                     } catch (DecryptionException e) {
737                         String msg = "Decryption attempt using credential from standard KeyInfo resolver failed: ";
738                         log.debug(msg, e);
739                         continue;
740                     }
741                 }
742             } catch (SecurityException e) {
743                 log.error("Error resolving credentials from EncryptedData KeyInfo", e);
744             }
745         }
746         return null;
747     }
748 
749     /**
750      * Attempt to decrypt by resolving the decryption key by first resolving EncryptedKeys, and using the KEK credential
751      * resolver to resolve the key decryption for each.
752      * 
753      * @param encryptedData the encrypted data to decrypt
754      * @param algorithm the algorithm of the key to be decrypted
755      * @return the decrypted document fragment, or null if decryption key could not be resolved or decryption failed
756      */
757     private DocumentFragment decryptUsingResolvedEncryptedKey(EncryptedData encryptedData, String algorithm) {
758         if (encKeyResolver != null) {
759             for (EncryptedKey encryptedKey : encKeyResolver.resolve(encryptedData)) {
760                 try {
761                     Key decryptedKey = decryptKey(encryptedKey, algorithm);
762                     return decryptDataToDOM(encryptedData, decryptedKey);
763                 } catch (DecryptionException e) {
764                     String msg = "Attempt to decrypt EncryptedData using key extracted from EncryptedKey failed: ";
765                     log.debug(msg, e);
766                     continue;
767                 }
768             }
769         }
770         return null;
771     }
772 
773     /**
774      * Parse the specified input stream in a DOM DocumentFragment, owned by the specified Document.
775      * 
776      * @param input the InputStream to parse
777      * @param owningDocument the Document which will own the returned DocumentFragment
778      * @return a DocumentFragment
779      * @throws DecryptionException thrown if there is an error parsing the input stream
780      */
781     private DocumentFragment parseInputStream(InputStream input, Document owningDocument) throws DecryptionException {
782         // Since Xerces currently seems not to handle parsing into a DocumentFragment
783         // without a bit hackery, use this to simulate, so we can keep the API
784         // the way it hopefully will look in the future. Obviously this only works for
785         // input streams containing valid XML instances, not fragments.
786 
787         Document newDocument = null;
788         try {
789             newDocument = parserPool.parse(input);
790         } catch (XMLParserException e) {
791             log.error("Error parsing decrypted input stream", e);
792             throw new DecryptionException("Error parsing input stream", e);
793         }
794 
795         Element element = newDocument.getDocumentElement();
796         owningDocument.adoptNode(element);
797 
798         DocumentFragment container = owningDocument.createDocumentFragment();
799         container.appendChild(element);
800 
801         return container;
802     }
803 
804     /**
805      * Utility method to build a new set of credential criteria based on the KeyInfo of an EncryptedData or
806      * EncryptedKey, and any additional static criteria which might have been supplied to the decrypter.
807      * 
808      * @param encryptedType an EncryptedData or EncryptedKey for which to resolve decryption credentials
809      * @param staticCriteria static set of credential criteria to add to the new criteria set
810      * @return the new credential criteria set
811      */
812     private CriteriaSet buildCredentialCriteria(EncryptedType encryptedType, CriteriaSet staticCriteria) {
813 
814         CriteriaSet newCriteriaSet = new CriteriaSet();
815 
816         // This is the main criteria based on the encrypted type's KeyInfo
817         newCriteriaSet.add(new KeyInfoCriteria(encryptedType.getKeyInfo()));
818 
819         // Also attemtpt to dynamically construct key criteria based on information
820         // in the encrypted object
821         Set<Criteria> keyCriteria = buildKeyCriteria(encryptedType);
822         if (keyCriteria != null && !keyCriteria.isEmpty()) {
823             newCriteriaSet.addAll(keyCriteria);
824         }
825 
826         // Add any static criteria which may have been supplied to the decrypter
827         if (staticCriteria != null && !staticCriteria.isEmpty()) {
828             newCriteriaSet.addAll(staticCriteria);
829         }
830 
831         // If don't have a usage criteria yet from static criteria, add encryption usage
832         if (!newCriteriaSet.contains(UsageCriteria.class)) {
833             newCriteriaSet.add(new UsageCriteria(UsageType.ENCRYPTION));
834         }
835 
836         return newCriteriaSet;
837     }
838 
839     /**
840      * Build decryption key credential criteria according to information in the encrypted type object.
841      * 
842      * @param encryptedType the encrypted type from which to deduce decryption key criteria
843      * @return a set of credential criteria pertaining to the decryption key
844      */
845     private Set<Criteria> buildKeyCriteria(EncryptedType encryptedType) {
846         EncryptionMethod encMethod = encryptedType.getEncryptionMethod();
847         if (encMethod == null) {
848             // This element is optional
849             return Collections.emptySet();
850         }
851         String encAlgorithmURI = DatatypeHelper.safeTrimOrNullString(encMethod.getAlgorithm());
852         if (encAlgorithmURI == null) {
853             return Collections.emptySet();
854         }
855 
856         Set<Criteria> critSet = new HashSet<Criteria>(2);
857 
858         KeyAlgorithmCriteria algoCrit = buildKeyAlgorithmCriteria(encAlgorithmURI);
859         if (algoCrit != null) {
860             critSet.add(algoCrit);
861             log.debug("Added decryption key algorithm criteria: {}", algoCrit.getKeyAlgorithm());
862         }
863 
864         KeyLengthCriteria lengthCrit = buildKeyLengthCriteria(encAlgorithmURI);
865         if (lengthCrit != null) {
866             critSet.add(lengthCrit);
867             log.debug("Added decryption key length criteria from EncryptionMethod algorithm URI: {}", lengthCrit
868                     .getKeyLength());
869         } else {
870             if (encMethod.getKeySize() != null && encMethod.getKeySize().getValue() != null) {
871                 lengthCrit = new KeyLengthCriteria(encMethod.getKeySize().getValue());
872                 critSet.add(lengthCrit);
873                 log.debug("Added decryption key length criteria from EncryptionMethod/KeySize: {}", lengthCrit
874                         .getKeyLength());
875             }
876         }
877 
878         return critSet;
879     }
880 
881     /**
882      * Dynamically construct key algorithm credential criteria based on the specified algorithm URI.
883      * 
884      * @param encAlgorithmURI the algorithm URI
885      * @return a new key algorithm credential criteria instance, or null if criteria could not be determined
886      */
887     private KeyAlgorithmCriteria buildKeyAlgorithmCriteria(String encAlgorithmURI) {
888         if (DatatypeHelper.isEmpty(encAlgorithmURI)) {
889             return null;
890         }
891 
892         String jcaKeyAlgorithm = SecurityHelper.getKeyAlgorithmFromURI(encAlgorithmURI);
893         if (!DatatypeHelper.isEmpty(jcaKeyAlgorithm)) {
894             return new KeyAlgorithmCriteria(jcaKeyAlgorithm);
895         }
896 
897         return null;
898     }
899 
900     /**
901      * Dynamically construct key length credential criteria based on the specified algorithm URI.
902      * 
903      * @param encAlgorithmURI the algorithm URI
904      * @return a new key length credential criteria instance, or null if the value could not be determined
905      */
906     private KeyLengthCriteria buildKeyLengthCriteria(String encAlgorithmURI) {
907         if (!DatatypeHelper.isEmpty(encAlgorithmURI)) {
908             return null;
909         }
910 
911         Integer keyLength = SecurityHelper.getKeyLengthFromURI(encAlgorithmURI);
912         if (keyLength != null) {
913             return new KeyLengthCriteria(keyLength);
914         }
915 
916         return null;
917     }
918 
919     /**
920      * Ensure that the XMLObject is marshalled.
921      * 
922      * @param xmlObject the object to check and marshall
923      * @throws DecryptionException thrown if there is an error when marshalling the XMLObject
924      */
925     protected void checkAndMarshall(XMLObject xmlObject) throws DecryptionException {
926         Element targetElement = xmlObject.getDOM();
927         if (targetElement == null) {
928             Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(xmlObject);
929             try {
930                 targetElement = marshaller.marshall(xmlObject);
931             } catch (MarshallingException e) {
932                 log.error("Error marshalling target XMLObject", e);
933                 throw new DecryptionException("Error marshalling target XMLObject", e);
934             }
935         }
936     }
937 
938     /*
939      * NOTE: this currently won't work because Xerces doesn't implement LSParser.parseWithContext(). Hopefully they will
940      * in the future.
941      */
942     /*
943      * private DocumentFragment parseInputStreamLS(InputStream input, Document owningDocument) throws
944      * DecryptionException {
945      * 
946      * DOMImplementationLS domImpl = getDOMImplemenationLS();
947      * 
948      * LSParser parser = domImpl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null); if (parser == null) { throw
949      * new DecryptionException("LSParser was null"); }
950      * 
951      * //DOMConfiguration config=parser.getDomConfig(); //DOMErrorHandlerImpl errorHandler=new DOMErrorHandlerImpl();
952      * //config.setParameter("error-handler", errorHandler);
953      * 
954      * LSInput lsInput = domImpl.createLSInput(); if (lsInput == null) { throw new DecryptionException("LSInput was
955      * null"); } lsInput.setByteStream(input);
956      * 
957      * DocumentFragment container = owningDocument.createDocumentFragment(); //TODO Xerces currently doesn't support
958      * LSParser.parseWithContext() parser.parseWithContext(lsInput, container, LSParser.ACTION_REPLACE_CHILDREN);
959      * 
960      * return container; }
961      */
962 
963     /*
964      * private DOMImplementationLS getDOMImplemenationLS() throws DecryptionException { if (domImplLS != null) { return
965      * domImplLS; }
966      *  // get an instance of the DOMImplementation registry DOMImplementationRegistry registry; try { registry =
967      * DOMImplementationRegistry.newInstance(); } catch (ClassCastException e) { throw new DecryptionException("Error
968      * creating new error of DOMImplementationRegistry", e); } catch (ClassNotFoundException e) { throw new
969      * DecryptionException("Error creating new error of DOMImplementationRegistry", e); } catch (InstantiationException
970      * e) { throw new DecryptionException("Error creating new error of DOMImplementationRegistry", e); } catch
971      * (IllegalAccessException e) { throw new DecryptionException("Error creating new error of
972      * DOMImplementationRegistry", e); }
973      *  // get a new instance of the DOM Level 3 Load/Save implementation DOMImplementationLS newDOMImplLS =
974      * (DOMImplementationLS) registry.getDOMImplementation("LS 3.0"); if (newDOMImplLS == null) { throw new
975      * DecryptionException("No LS DOMImplementation could be found"); } else { domImplLS = newDOMImplLS; }
976      * 
977      * return domImplLS; }
978      */
979 
980     /*
981      * Initialize the Apache XML security library if it hasn't been already
982      */
983     static {
984         if (!Init.isInitialized()) {
985             Init.init();
986         }
987     }
988 
989 }