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 }