View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.ws.security.util;
21  
22  import org.apache.ws.security.SOAP11Constants;
23  import org.apache.ws.security.SOAP12Constants;
24  import org.apache.ws.security.SOAPConstants;
25  import org.apache.ws.security.WSConstants;
26  import org.apache.ws.security.WSDataRef;
27  import org.apache.ws.security.WSEncryptionPart;
28  import org.apache.ws.security.WSSecurityEngineResult;
29  import org.apache.ws.security.WSSecurityException;
30  import org.apache.ws.security.WSSConfig;
31  import org.apache.ws.security.handler.WSHandlerConstants;
32  import org.apache.ws.security.message.CallbackLookup;
33  import org.apache.xml.security.algorithms.JCEMapper;
34  import org.w3c.dom.Attr;
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.NamedNodeMap;
38  import org.w3c.dom.Node;
39  import org.w3c.dom.Text;
40  
41  import javax.crypto.Cipher;
42  import javax.crypto.NoSuchPaddingException;
43  import javax.crypto.SecretKey;
44  import javax.crypto.spec.SecretKeySpec;
45  import javax.xml.crypto.dom.DOMCryptoContext;
46  import javax.xml.namespace.QName;
47  
48  import java.security.MessageDigest;
49  import java.security.NoSuchAlgorithmException;
50  import java.security.SecureRandom;
51  import java.util.ArrayList;
52  import java.util.Collections;
53  import java.util.HashSet;
54  import java.util.Iterator;
55  import java.util.List;
56  import java.util.Set;
57  
58  /**
59   * WS-Security Utility methods. <p/>
60   * 
61   * @author Davanum Srinivas (dims@yahoo.com).
62   */
63  public final class WSSecurityUtil {
64      private static org.apache.commons.logging.Log log = 
65          org.apache.commons.logging.LogFactory.getLog(WSSecurityUtil.class);
66  
67      /**
68       * A cached pseudo-random number generator
69       * NB. On some JVMs, caching this random number
70       * generator is required to overcome punitive
71       * overhead.
72       */
73      private static SecureRandom random = null;
74      
75      /**
76       * A cached MessageDigest object
77       */
78      private static MessageDigest digest = null;
79      
80      private WSSecurityUtil() {
81          // Complete
82      }
83      
84      /**
85       * Returns the first WS-Security header element for a given actor. Only one
86       * WS-Security header is allowed for an actor.
87       * 
88       * @param doc
89       * @param actor
90       * @return the <code>wsse:Security</code> element or <code>null</code>
91       *         if not such element found
92       */
93      public static Element getSecurityHeader(Document doc, String actor) throws WSSecurityException {
94          String soapNamespace = WSSecurityUtil.getSOAPNamespace(doc.getDocumentElement());
95          Element soapHeaderElement = 
96              getDirectChildElement(
97                  doc.getDocumentElement(), 
98                  WSConstants.ELEM_HEADER, 
99                  soapNamespace
100             );
101         if (soapHeaderElement == null) { // no SOAP header at all
102             return null;
103         }
104         
105         String actorLocal = WSConstants.ATTR_ACTOR;
106         if (WSConstants.URI_SOAP12_ENV.equals(soapNamespace)) {
107             actorLocal = WSConstants.ATTR_ROLE;
108         }
109         
110         //
111         // Iterate through the security headers
112         //
113         Element foundSecurityHeader = null;
114         for (
115             Node currentChild = soapHeaderElement.getFirstChild(); 
116             currentChild != null; 
117             currentChild = currentChild.getNextSibling()
118         ) {
119             if (Node.ELEMENT_NODE == currentChild.getNodeType()
120                 && WSConstants.WSSE_LN.equals(currentChild.getLocalName())
121                 && WSConstants.WSSE_NS.equals(currentChild.getNamespaceURI())) {
122                 
123                 Element elem = (Element)currentChild;
124                 Attr attr = elem.getAttributeNodeNS(soapNamespace, actorLocal);
125                 String hActor = (attr != null) ? attr.getValue() : null;
126 
127                 if (WSSecurityUtil.isActorEqual(actor, hActor)) {
128                     if (foundSecurityHeader != null) {
129                         if (log.isDebugEnabled()) {
130                             log.debug(
131                                 "Two or more security headers have the same actor name: " + actor
132                             );
133                         }
134                         throw new WSSecurityException(WSSecurityException.INVALID_SECURITY);
135                     }
136                     foundSecurityHeader = elem;
137                 }
138             }
139         }
140         return foundSecurityHeader;
141     }
142 
143 
144     /**
145      * Compares two actor strings and returns true if these are equal. Takes
146      * care of the null length strings and uses ignore case.
147      * 
148      * @param actor
149      * @param hActor
150      * @return true is the actor arguments are equal
151      */
152     public static boolean isActorEqual(String actor, String hActor) {
153         if (((hActor == null) || (hActor.length() == 0)) 
154             && ((actor == null) || (actor.length() == 0))) {
155             return true;
156         }
157         
158         if ((hActor != null) && (actor != null) && hActor.equalsIgnoreCase(actor)) {
159             return true;
160         }
161         
162         return false;
163     }
164 
165     
166     /**
167      * Gets a direct child with specified localname and namespace. <p/>
168      * 
169      * @param parentNode the node where to start the search
170      * @param localName local name of the child to get
171      * @param namespace the namespace of the child to get
172      * @return the node or <code>null</code> if not such node found
173      */
174     public static Element getDirectChildElement(
175         Node parentNode, 
176         String localName,
177         String namespace
178     ) {
179         if (parentNode == null) {
180             return null;
181         }
182         for (
183             Node currentChild = parentNode.getFirstChild(); 
184             currentChild != null; 
185             currentChild = currentChild.getNextSibling()
186         ) {
187             if (Node.ELEMENT_NODE == currentChild.getNodeType()
188                 && localName.equals(currentChild.getLocalName())
189                 && namespace.equals(currentChild.getNamespaceURI())) {
190                 return (Element)currentChild;
191             }
192         }
193         return null;
194     }
195     
196     
197     /**
198      * Gets all direct children with specified localname and namespace. <p/>
199      * 
200      * @param fNode the node where to start the search
201      * @param localName local name of the children to get
202      * @param namespace the namespace of the children to get
203      * @return the list of nodes or <code>null</code> if not such nodes are found
204      */
205     public static List<Element> getDirectChildElements(
206         Node fNode, 
207         String localName,
208         String namespace
209     ) {
210         List<Element> children = new ArrayList<Element>();
211         for (
212             Node currentChild = fNode.getFirstChild(); 
213             currentChild != null; 
214             currentChild = currentChild.getNextSibling()
215         ) {
216             if (Node.ELEMENT_NODE == currentChild.getNodeType()
217                 && localName.equals(currentChild.getLocalName())
218                 && namespace.equals(currentChild.getNamespaceURI())) {
219                 children.add((Element)currentChild);
220             }
221         }
222         return children;
223     }
224     
225 
226     /**
227      * return the first soap "Body" element. <p/>
228      * 
229      * @param doc
230      * @return the body element or <code>null</code> if document does not
231      *         contain a SOAP body
232      */
233     public static Element findBodyElement(Document doc) {
234         //
235         // Find the SOAP Envelope NS. Default to SOAP11 NS
236         //
237         Element docElement = doc.getDocumentElement();
238         String ns = docElement.getNamespaceURI();
239         return getDirectChildElement(docElement, WSConstants.ELEM_BODY, ns);
240     }
241     
242     
243     /**
244      * Find the DOM Element in the SOAP Envelope that is referenced by the 
245      * WSEncryptionPart argument. The "Id" is used before the Element localname/namespace.
246      * 
247      * @param part The WSEncryptionPart object corresponding to the DOM Element(s) we want
248      * @param callbackLookup The CallbackLookup object used to find Elements
249      * @param doc The owning document
250      * @return the DOM Element in the SOAP Envelope that is found
251      */
252     public static List<Element> findElements(
253         WSEncryptionPart part, CallbackLookup callbackLookup, Document doc
254     ) throws WSSecurityException {
255         // See if the DOM Element is stored in the WSEncryptionPart first
256         if (part.getElement() != null) {
257             return Collections.singletonList(part.getElement());
258         }
259         
260         // Next try to find the Element via its wsu:Id
261         String id = part.getId();
262         if (id != null) {
263             Element foundElement = callbackLookup.getElement(id, null, false);
264             return Collections.singletonList(foundElement);
265         }
266         // Otherwise just lookup all elements with the localname/namespace
267         return callbackLookup.getElements(part.getName(), part.getNamespace());
268     }
269     
270     /**
271      * Returns the first element that matches <code>name</code> and
272      * <code>namespace</code>. <p/> This is a replacement for a XPath lookup
273      * <code>//name</code> with the given namespace. It's somewhat faster than
274      * XPath, and we do not deal with prefixes, just with the real namespace URI
275      * 
276      * @param startNode Where to start the search
277      * @param name Local name of the element
278      * @param namespace Namespace URI of the element
279      * @return The found element or <code>null</code>
280      */
281     public static Element findElement(Node startNode, String name, String namespace) {
282         //
283         // Replace the formerly recursive implementation with a depth-first-loop
284         // lookup
285         //
286         if (startNode == null) {
287             return null;
288         }
289         Node startParent = startNode.getParentNode();
290         Node processedNode = null;
291 
292         while (startNode != null) {
293             // start node processing at this point
294             if (startNode.getNodeType() == Node.ELEMENT_NODE
295                 && startNode.getLocalName().equals(name)) {
296                 String ns = startNode.getNamespaceURI();
297                 if (ns != null && ns.equals(namespace)) {
298                     return (Element)startNode;
299                 }
300 
301                 if ((namespace == null || namespace.length() == 0)
302                     && (ns == null || ns.length() == 0)) {
303                     return (Element)startNode;
304                 }
305             }
306             processedNode = startNode;
307             startNode = startNode.getFirstChild();
308 
309             // no child, this node is done.
310             if (startNode == null) {
311                 // close node processing, get sibling
312                 startNode = processedNode.getNextSibling();
313             }
314             // no more siblings, get parent, all children
315             // of parent are processed.
316             while (startNode == null) {
317                 processedNode = processedNode.getParentNode();
318                 if (processedNode == startParent) {
319                     return null;
320                 }
321                 // close parent node processing (processed node now)
322                 startNode = processedNode.getNextSibling();
323             }
324         }
325         return null;
326     }
327     
328     /**
329      * Returns all elements that match <code>name</code> and <code>namespace</code>. 
330      * <p/> This is a replacement for a XPath lookup
331      * <code>//name</code> with the given namespace. It's somewhat faster than
332      * XPath, and we do not deal with prefixes, just with the real namespace URI
333      * 
334      * @param startNode Where to start the search
335      * @param name Local name of the element
336      * @param namespace Namespace URI of the element
337      * @return The found elements (or an empty list)
338      */
339     public static List<Element> findElements(Node startNode, String name, String namespace) {
340         //
341         // Replace the formerly recursive implementation with a depth-first-loop
342         // lookup
343         //
344         if (startNode == null) {
345             return null;
346         }
347         Node startParent = startNode.getParentNode();
348         Node processedNode = null;
349 
350         List<Element> foundNodes = new ArrayList<Element>();
351         while (startNode != null) {
352             // start node processing at this point
353             if (startNode.getNodeType() == Node.ELEMENT_NODE
354                 && startNode.getLocalName().equals(name)) {
355                 String ns = startNode.getNamespaceURI();
356                 if (ns != null && ns.equals(namespace)) {
357                     foundNodes.add((Element)startNode);
358                 }
359 
360                 if ((namespace == null || namespace.length() == 0)
361                     && (ns == null || ns.length() == 0)) {
362                     foundNodes.add((Element)startNode);
363                 }
364             }
365             processedNode = startNode;
366             startNode = startNode.getFirstChild();
367 
368             // no child, this node is done.
369             if (startNode == null) {
370                 // close node processing, get sibling
371                 startNode = processedNode.getNextSibling();
372             }
373             // no more siblings, get parent, all children
374             // of parent are processed.
375             while (startNode == null) {
376                 processedNode = processedNode.getParentNode();
377                 if (processedNode == startParent) {
378                     return foundNodes;
379                 }
380                 // close parent node processing (processed node now)
381                 startNode = processedNode.getNextSibling();
382             }
383         }
384         return foundNodes;
385     }
386     
387     /**
388      * Returns the single SAMLAssertion element that contains an AssertionID/ID that
389      * matches the supplied parameter.
390      * 
391      * @param startNode Where to start the search
392      * @param value Value of the AssertionID/ID attribute
393      * @return The found element if there was exactly one match, or
394      *         <code>null</code> otherwise
395      */
396     public static Element findSAMLAssertionElementById(Node startNode, String value) {
397         Element foundElement = null;
398 
399         //
400         // Replace the formerly recursive implementation with a depth-first-loop
401         // lookup
402         //
403         if (startNode == null) {
404             return null;
405         }
406         Node startParent = startNode.getParentNode();
407         Node processedNode = null;
408 
409         while (startNode != null) {
410             // start node processing at this point
411             if (startNode.getNodeType() == Node.ELEMENT_NODE) {
412                 Element se = (Element) startNode;
413                 if ((se.hasAttribute("ID") && value.equals(se.getAttribute("ID")))
414                     || (se.hasAttribute("AssertionID") 
415                         && value.equals(se.getAttribute("AssertionID")))) {
416                     if (foundElement == null) {
417                         foundElement = se; // Continue searching to find duplicates
418                     } else {
419                         log.warn("Multiple elements with the same 'ID' attribute value!");
420                         return null;
421                     }
422                 }
423             }
424 
425             processedNode = startNode;
426             startNode = startNode.getFirstChild();
427 
428             // no child, this node is done.
429             if (startNode == null) {
430                 // close node processing, get sibling
431                 startNode = processedNode.getNextSibling();
432             }
433             // no more siblings, get parent, all children
434             // of parent are processed.
435             while (startNode == null) {
436                 processedNode = processedNode.getParentNode();
437                 if (processedNode == startParent) {
438                     return foundElement;
439                 }
440                 // close parent node processing (processed node now)
441                 startNode = processedNode.getNextSibling();
442             }
443         }
444         return foundElement;
445     }
446     
447 
448     /**
449      * Returns the single element that contains an Id with value
450      * <code>uri</code> and <code>namespace</code>. The Id can be either a wsu:Id or an Id
451      * with no namespace. This is a replacement for a XPath Id lookup with the given namespace. 
452      * It's somewhat faster than XPath, and we do not deal with prefixes, just with the real
453      * namespace URI
454      * 
455      * If checkMultipleElements is true and there are multiple elements, we log a 
456      * warning and return null as this can be used to get around the signature checking.
457      * 
458      * @param startNode Where to start the search
459      * @param value Value of the Id attribute
460      * @param checkMultipleElements If true then go through the entire tree and return 
461      *        null if there are multiple elements with the same Id
462      * @return The found element if there was exactly one match, or
463      *         <code>null</code> otherwise
464      */
465     public static Element findElementById(
466         Node startNode, String value, boolean checkMultipleElements
467     ) {
468         //
469         // Replace the formerly recursive implementation with a depth-first-loop lookup
470         //
471         Node startParent = startNode.getParentNode();
472         Node processedNode = null;
473         Element foundElement = null;
474         String id = getIDFromReference(value);
475 
476         while (startNode != null) {
477             // start node processing at this point
478             if (startNode.getNodeType() == Node.ELEMENT_NODE) {
479                 Element se = (Element) startNode;
480                 // Try the wsu:Id first
481                 String attributeNS = se.getAttributeNS(WSConstants.WSU_NS, "Id");
482                 if ("".equals(attributeNS) || !id.equals(attributeNS)) {
483                     attributeNS = se.getAttributeNS(null, "Id");
484                 }
485                 if (!"".equals(attributeNS) && id.equals(attributeNS)) {
486                     if (!checkMultipleElements) {
487                         return se;
488                     } else if (foundElement == null) {
489                         foundElement = se; // Continue searching to find duplicates
490                     } else {
491                         log.warn("Multiple elements with the same 'Id' attribute value!");
492                         return null;
493                     }
494                 }
495             }
496 
497             processedNode = startNode;
498             startNode = startNode.getFirstChild();
499 
500             // no child, this node is done.
501             if (startNode == null) {
502                 // close node processing, get sibling
503                 startNode = processedNode.getNextSibling();
504             }
505             // no more siblings, get parent, all children
506             // of parent are processed.
507             while (startNode == null) {
508                 processedNode = processedNode.getParentNode();
509                 if (processedNode == startParent) {
510                     return foundElement;
511                 }
512                 // close parent node processing (processed node now)
513                 startNode = processedNode.getNextSibling();
514             }
515         }
516         return foundElement;
517     }
518 
519     /**
520      * Set a namespace/prefix on an element if it is not set already. First off, it
521      * searches for the element for the prefix associated with the specified
522      * namespace. If the prefix isn't null, then this is returned. Otherwise, it
523      * creates a new attribute using the namespace/prefix passed as parameters.
524      * 
525      * @param element
526      * @param namespace
527      * @param prefix
528      * @return the prefix associated with the set namespace
529      */
530     public static String setNamespace(Element element, String namespace, String prefix) {
531         String pre = getPrefixNS(namespace, element);
532         if (pre != null) {
533             return pre;
534         }
535         element.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:" + prefix, namespace);
536         return prefix;
537     }
538 
539     /*
540      * The following methods were copied over from axis.utils.XMLUtils and adapted
541      */
542     public static String getPrefixNS(String uri, Node e) {
543         while (e != null && (e.getNodeType() == Element.ELEMENT_NODE)) {
544             NamedNodeMap attrs = e.getAttributes();
545             for (int n = 0; n < attrs.getLength(); n++) {
546                 Attr a = (Attr) attrs.item(n);
547                 String name = a.getName();
548                 if (name.startsWith("xmlns:") && a.getNodeValue().equals(uri)) {
549                     return name.substring("xmlns:".length());
550                 }
551             }
552             e = e.getParentNode();
553         }
554         return null;
555     }
556 
557     public static String getNamespace(String prefix, Node e) {
558         while (e != null && (e.getNodeType() == Node.ELEMENT_NODE)) {
559             Attr attr = null;
560             if (prefix == null) {
561                 attr = ((Element) e).getAttributeNode("xmlns");
562             } else {
563                 attr = ((Element) e).getAttributeNodeNS(WSConstants.XMLNS_NS, prefix);
564             }
565             if (attr != null) {
566                 return attr.getValue();
567             }
568             e = e.getParentNode();
569         }
570         return null;
571     }
572 
573     /**
574      * Return a QName when passed a string like "foo:bar" by mapping the "foo"
575      * prefix to a namespace in the context of the given Node.
576      * 
577      * @return a QName generated from the given string representation
578      */
579     public static QName getQNameFromString(String str, Node e) {
580         return getQNameFromString(str, e, false);
581     }
582 
583     /**
584      * Return a QName when passed a string like "foo:bar" by mapping the "foo"
585      * prefix to a namespace in the context of the given Node. If default
586      * namespace is found it is returned as part of the QName.
587      * 
588      * @return a QName generated from the given string representation
589      */
590     public static QName getFullQNameFromString(String str, Node e) {
591         return getQNameFromString(str, e, true);
592     }
593 
594     private static QName getQNameFromString(String str, Node e, boolean defaultNS) {
595         if (str == null || e == null) {
596             return null;
597         }
598         int idx = str.indexOf(':');
599         if (idx > -1) {
600             String prefix = str.substring(0, idx);
601             String ns = getNamespace(prefix, e);
602             if (ns == null) {
603                 return null;
604             }
605             return new QName(ns, str.substring(idx + 1));
606         } else {
607             if (defaultNS) {
608                 String ns = getNamespace(null, e);
609                 if (ns != null) {
610                     return new QName(ns, str);
611                 }
612             }
613             return new QName("", str);
614         }
615     }
616 
617     /**
618      * Return a string for a particular QName, mapping a new prefix if
619      * necessary.
620      */
621     public static String getStringForQName(QName qname, Element e) {
622         String uri = qname.getNamespaceURI();
623         String prefix = getPrefixNS(uri, e);
624         if (prefix == null) {
625             int i = 1;
626             prefix = "ns" + i;
627             while (getNamespace(prefix, e) != null) {
628                 i++;
629                 prefix = "ns" + i;
630             }
631             e.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:" + prefix, uri);
632         }
633         return prefix + ":" + qname.getLocalPart();
634     }
635 
636     /**
637      * Turn a reference (eg "#5") into an ID (eg "5").
638      * 
639      * @param ref
640      * @return ref trimmed and with the leading "#" removed, or null if not
641      *         correctly formed
642      */
643     public static String getIDFromReference(String ref) {
644         String id = ref.trim();
645         if (id.length() == 0) {
646             return null;
647         }
648         if (id.charAt(0) == '#') {
649             id = id.substring(1);
650         }
651         return id;
652     }
653     
654     /**
655      * create a new element in the same namespace <p/>
656      * 
657      * @param parent for the new element
658      * @param localName of the new element
659      * @return the new element
660      */
661     private static Element createElementInSameNamespace(Element parent, String localName) {
662         String qName = localName;
663         String prefix = parent.getPrefix();
664         if (prefix != null && prefix.length() > 0) {
665             qName = prefix + ":" + localName;
666         }
667          
668         String nsUri = parent.getNamespaceURI();
669         return parent.getOwnerDocument().createElementNS(nsUri, qName);
670     }
671 
672 
673     /**
674      * prepend a child element <p/>
675      * 
676      * @param parent element of this child element
677      * @param child the element to append
678      * @return the child element
679      */
680     public static Element prependChildElement(
681         Element parent,
682         Element child
683     ) {
684         Node firstChild = parent.getFirstChild();
685         if (firstChild == null) {
686             return (Element)parent.appendChild(child);
687         } else {
688             return (Element)parent.insertBefore(child, firstChild);
689         }
690     }
691 
692 
693     /**
694      * find the first ws-security header block <p/>
695      * 
696      * @param doc the DOM document (SOAP request)
697      * @param envelope the SOAP envelope
698      * @param doCreate if true create a new WSS header block if none exists
699      * @return the WSS header or null if none found and doCreate is false
700      */
701     public static Element findWsseSecurityHeaderBlock(
702         Document doc,
703         Element envelope, 
704         boolean doCreate
705     ) throws WSSecurityException {
706         return findWsseSecurityHeaderBlock(doc, envelope, null, doCreate);
707     }
708 
709     /**
710      * find a WS-Security header block for a given actor <p/>
711      * 
712      * @param doc the DOM document (SOAP request)
713      * @param envelope the SOAP envelope
714      * @param actor the actor (role) name of the WSS header
715      * @param doCreate if true create a new WSS header block if none exists
716      * @return the WSS header or null if none found and doCreate is false
717      */
718     public static Element findWsseSecurityHeaderBlock(
719         Document doc,
720         Element envelope,
721         String actor, 
722         boolean doCreate
723     ) throws WSSecurityException {
724         Element wsseSecurity = getSecurityHeader(doc, actor);
725         if (wsseSecurity != null) {
726             return wsseSecurity;
727         } else if (doCreate) {
728             String soapNamespace = WSSecurityUtil.getSOAPNamespace(envelope);
729             Element header = 
730                 getDirectChildElement(envelope, WSConstants.ELEM_HEADER, soapNamespace);
731             if (header == null) {
732                 header = createElementInSameNamespace(envelope, WSConstants.ELEM_HEADER);
733                 header = prependChildElement(envelope, header);
734             }
735             wsseSecurity = doc.createElementNS(WSConstants.WSSE_NS, "wsse:Security");
736             wsseSecurity.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:wsse", WSConstants.WSSE_NS);
737             return prependChildElement(header, wsseSecurity);
738         }
739         return null;
740     }
741 
742     /**
743      * create a base64 test node <p/>
744      * 
745      * @param doc the DOM document (SOAP request)
746      * @param data to encode
747      * @return a Text node containing the base64 encoded data
748      */
749     public static Text createBase64EncodedTextNode(Document doc, byte data[]) {
750         return doc.createTextNode(Base64.encode(data));
751     }
752 
753     public static SOAPConstants getSOAPConstants(Element startElement) {
754         Document doc = startElement.getOwnerDocument();
755         String ns = doc.getDocumentElement().getNamespaceURI();
756         if (WSConstants.URI_SOAP12_ENV.equals(ns)) {
757             return new SOAP12Constants();
758         }
759         return new SOAP11Constants();
760     }
761     
762     public static String getSOAPNamespace(Element startElement) {
763         return getSOAPConstants(startElement).getEnvelopeURI();
764     }
765     
766     
767     /**
768      * Convert the raw key bytes into a SecretKey object of type symEncAlgo.
769      */
770     public static SecretKey prepareSecretKey(String symEncAlgo, byte[] rawKey) {
771         // Do an additional check on the keysize required by the encryption algorithm
772         int size = 0;
773         try {
774             size = JCEMapper.getKeyLengthFromURI(symEncAlgo) / 8;
775         } catch (Exception e) {
776             // ignore - some unknown (to JCEMapper) encryption algorithm
777             if (log.isDebugEnabled()) {
778                 log.debug(e.getMessage());
779             }
780         }
781         String keyAlgorithm = JCEMapper.getJCEKeyAlgorithmFromURI(symEncAlgo);
782         SecretKeySpec keySpec;
783         if (size > 0) {
784             keySpec = 
785                 new SecretKeySpec(
786                     rawKey, 0, ((rawKey.length > size) ? size : rawKey.length), keyAlgorithm
787                 );
788         } else {
789             keySpec = new SecretKeySpec(rawKey, keyAlgorithm);
790         }
791         return (SecretKey)keySpec;
792     }
793 
794 
795     /**
796      * Translate the "cipherAlgo" URI to a JCE ID, and return a javax.crypto.Cipher instance
797      * of this type. 
798      */
799     public static Cipher getCipherInstance(String cipherAlgo)
800         throws WSSecurityException {
801         try {
802             String keyAlgorithm = JCEMapper.translateURItoJCEID(cipherAlgo);
803             return Cipher.getInstance(keyAlgorithm);
804         } catch (NoSuchPaddingException ex) {
805             throw new WSSecurityException(
806                 WSSecurityException.UNSUPPORTED_ALGORITHM, "unsupportedKeyTransp", 
807                 new Object[] { "No such padding: " + cipherAlgo }, ex
808             );
809         } catch (NoSuchAlgorithmException ex) {
810             // Check to see if an RSA OAEP MGF-1 with SHA-1 algorithm was requested
811             // Some JDKs don't support RSA/ECB/OAEPPadding
812             if (WSConstants.KEYTRANSPORT_RSAOEP.equals(cipherAlgo)) {
813                 try {
814                     return Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
815                 } catch (Exception e) {
816                     throw new WSSecurityException(
817                         WSSecurityException.UNSUPPORTED_ALGORITHM, "unsupportedKeyTransp",
818                         new Object[] { "No such algorithm: " + cipherAlgo }, e
819                     );
820                 }
821             } else {
822                 throw new WSSecurityException(
823                     WSSecurityException.UNSUPPORTED_ALGORITHM, "unsupportedKeyTransp",
824                     new Object[] { "No such algorithm: " + cipherAlgo }, ex
825                 );
826             }
827         }
828     }
829     
830 
831     /**
832      * Fetch the result of a given action from a given result list
833      * 
834      * @param resultList The result list to fetch an action from
835      * @param action The action to fetch
836      * @return The last result fetched from the result list, null if the result
837      *         could not be found
838      */
839     public static WSSecurityEngineResult fetchActionResult(
840         List<WSSecurityEngineResult> resultList, 
841         int action
842     ) {
843         WSSecurityEngineResult returnResult = null;
844         
845         for (WSSecurityEngineResult result : resultList) {
846             //
847             // Check the result of every action whether it matches the given action
848             //
849             int resultAction = 
850                 ((java.lang.Integer)result.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
851             if (resultAction == action) {
852                 returnResult = result;
853             }
854         }
855 
856         return returnResult;
857     }
858     
859 
860     /**
861      * Fetch the result of a given action from a given result list.
862      * 
863      * @param resultList The result list to fetch an action from
864      * @param action The action to fetch
865      * @param actionResultList where to store the found results data for the action
866      * @return The result fetched from the result list, null if the result
867      *         could not be found
868      */
869     public static List<WSSecurityEngineResult> fetchAllActionResults(
870         List<WSSecurityEngineResult> resultList,
871         int action, 
872         List<WSSecurityEngineResult> actionResultList
873     ) {
874         for (WSSecurityEngineResult result : resultList) {
875             //
876             // Check the result of every action whether it matches the given action
877             //
878             int resultAction = 
879                 ((java.lang.Integer)result.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
880             if (resultAction == action) {
881                 actionResultList.add(result);
882             }
883         }
884         return actionResultList;
885     }
886 
887     public static int decodeAction(
888         String action, 
889         List<Integer> actions
890     ) throws WSSecurityException {
891 
892         int doAction = 0;
893         if (action == null) {
894             return doAction;
895         }
896         String single[] = StringUtil.split(action, ' ');
897         for (int i = 0; i < single.length; i++) {
898             if (single[i].equals(WSHandlerConstants.NO_SECURITY)) {
899                 doAction = WSConstants.NO_SECURITY;
900                 return doAction;
901             } else if (single[i].equals(WSHandlerConstants.USERNAME_TOKEN)) {
902                 doAction |= WSConstants.UT;
903                 actions.add(Integer.valueOf(WSConstants.UT));
904             } else if (single[i].equals(WSHandlerConstants.USERNAME_TOKEN_NO_PASSWORD)) {
905                 doAction |= WSConstants.UT_NOPASSWORD;
906                 actions.add(Integer.valueOf(WSConstants.UT_NOPASSWORD));
907             } else if (single[i].equals(WSHandlerConstants.SIGNATURE)) {
908                 doAction |= WSConstants.SIGN;
909                 actions.add(Integer.valueOf(WSConstants.SIGN));
910             } else if (single[i].equals(WSHandlerConstants.ENCRYPT)) {
911                 doAction |= WSConstants.ENCR;
912                 actions.add(Integer.valueOf(WSConstants.ENCR));
913             } else if (single[i].equals(WSHandlerConstants.SAML_TOKEN_UNSIGNED)) {
914                 doAction |= WSConstants.ST_UNSIGNED;
915                 actions.add(Integer.valueOf(WSConstants.ST_UNSIGNED));
916             } else if (single[i].equals(WSHandlerConstants.SAML_TOKEN_SIGNED)) {
917                 doAction |= WSConstants.ST_SIGNED;
918                 actions.add(Integer.valueOf(WSConstants.ST_SIGNED));
919             } else if (single[i].equals(WSHandlerConstants.TIMESTAMP)) {
920                 doAction |= WSConstants.TS;
921                 actions.add(Integer.valueOf(WSConstants.TS));
922             } else if (single[i].equals(WSHandlerConstants.SIGN_WITH_UT_KEY)) {
923                 doAction |= WSConstants.UT_SIGN;
924                 actions.add(Integer.valueOf(WSConstants.UT_SIGN));
925             } else if (single[i].equals(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION)) {
926                 doAction |= WSConstants.SC;
927                 actions.add(Integer.valueOf(WSConstants.SC));
928             } else {
929                 throw new WSSecurityException(
930                     "Unknown action defined: " + single[i]
931                 );
932             }
933         }
934         return doAction;
935     }
936     
937     
938     /**
939      * Decode an action String. This method should only be called on the outbound side.
940      * @param action The initial String of actions to perform
941      * @param actions The list of created actions that will be performed
942      * @param wssConfig This object holds the list of custom actions to be performed.
943      * @return The or'd integer of all the actions (apart from the custom actions)
944      * @throws WSSecurityException
945      */
946     public static int decodeAction(
947         String action, 
948         List<Integer> actions,
949         WSSConfig wssConfig
950     ) throws WSSecurityException {
951 
952         int doAction = 0;
953         if (action == null) {
954             return doAction;
955         }
956         String single[] = StringUtil.split(action, ' ');
957         for (int i = 0; i < single.length; i++) {
958             if (single[i].equals(WSHandlerConstants.NO_SECURITY)) {
959                 doAction = WSConstants.NO_SECURITY;
960                 return doAction;
961             } else if (single[i].equals(WSHandlerConstants.USERNAME_TOKEN)) {
962                 doAction |= WSConstants.UT;
963                 actions.add(Integer.valueOf(WSConstants.UT));
964             } else if (single[i].equals(WSHandlerConstants.SIGNATURE)) {
965                 doAction |= WSConstants.SIGN;
966                 actions.add(Integer.valueOf(WSConstants.SIGN));
967             } else if (single[i].equals(WSHandlerConstants.ENCRYPT)) {
968                 doAction |= WSConstants.ENCR;
969                 actions.add(Integer.valueOf(WSConstants.ENCR));
970             } else if (single[i].equals(WSHandlerConstants.SAML_TOKEN_UNSIGNED)) {
971                 doAction |= WSConstants.ST_UNSIGNED;
972                 actions.add(Integer.valueOf(WSConstants.ST_UNSIGNED));
973             } else if (single[i].equals(WSHandlerConstants.SAML_TOKEN_SIGNED)) {
974                 doAction |= WSConstants.ST_SIGNED;
975                 actions.add(Integer.valueOf(WSConstants.ST_SIGNED));
976             } else if (single[i].equals(WSHandlerConstants.TIMESTAMP)) {
977                 doAction |= WSConstants.TS;
978                 actions.add(Integer.valueOf(WSConstants.TS));
979             } else if (single[i].equals(WSHandlerConstants.SIGN_WITH_UT_KEY)) {
980                 doAction |= WSConstants.UT_SIGN;
981                 actions.add(Integer.valueOf(WSConstants.UT_SIGN));
982             } else if (single[i].equals(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION)) {
983                 doAction |= WSConstants.SC;
984                 actions.add(Integer.valueOf(WSConstants.SC));
985             } else {
986                 try {
987                     int parsedAction = Integer.parseInt(single[i]);
988                     if (wssConfig.getAction(parsedAction) == null) {
989                         throw new WSSecurityException(
990                             "Unknown action defined: " + single[i]
991                         );
992                     }
993                     actions.add(Integer.valueOf(parsedAction));
994                 } catch (NumberFormatException ex) {
995                     throw new WSSecurityException(
996                         "Unknown action defined: " + single[i]
997                     );
998                 }
999             }
1000         }
1001         return doAction;
1002     }
1003 
1004     /**
1005      * Returns the length of the key in # of bytes
1006      * 
1007      * @param algorithm
1008      * @return the key length
1009      */
1010     public static int getKeyLength(String algorithm) throws WSSecurityException {
1011         if (algorithm.equals(WSConstants.TRIPLE_DES)) {
1012             return 24;
1013         } else if (algorithm.equals(WSConstants.AES_128)) {
1014             return 16;
1015         } else if (algorithm.equals(WSConstants.AES_192)) {
1016             return 24;
1017         } else if (algorithm.equals(WSConstants.AES_256)) {
1018             return 32;
1019         } else if (WSConstants.HMAC_SHA1.equals(algorithm)) {
1020             return 20;
1021         } else if (WSConstants.HMAC_SHA256.equals(algorithm)) {
1022             return 32;
1023         } else if (WSConstants.HMAC_SHA384.equals(algorithm)) {
1024             return 48;
1025         } else if (WSConstants.HMAC_SHA512.equals(algorithm)) {
1026             return 64;
1027         } else if (WSConstants.HMAC_MD5.equals(algorithm)) {
1028             return 16;
1029         } else {
1030             throw new WSSecurityException(
1031                 WSSecurityException.UNSUPPORTED_ALGORITHM, null, null, null
1032             );
1033         }
1034     }
1035 
1036     /**
1037      * Generate a nonce of the given length using the SHA1PRNG algorithm. The SecureRandom
1038      * instance that backs this method is cached for efficiency.
1039      * 
1040      * @return a nonce of the given length
1041      * @throws WSSecurityException
1042      */
1043     public static synchronized byte[] generateNonce(int length) throws WSSecurityException {
1044         try {
1045             if (random == null) {
1046                 random = SecureRandom.getInstance("SHA1PRNG");
1047             }
1048             byte[] temp = new byte[length];
1049             random.nextBytes(temp);
1050             return temp;
1051         } catch (Exception ex) {
1052             throw new WSSecurityException(
1053                 "Error in generating nonce of length " + length, ex
1054             );
1055         }
1056     }
1057     
1058     /**
1059      * Generate a (SHA1) digest of the input bytes. The MessageDigest instance that backs this
1060      * method is cached for efficiency.  
1061      * @param inputBytes the bytes to digest
1062      * @return the digest of the input bytes
1063      * @throws WSSecurityException
1064      */
1065     public static synchronized byte[] generateDigest(byte[] inputBytes) throws WSSecurityException {
1066         try {
1067             if (digest == null) {
1068                 digest = MessageDigest.getInstance("SHA-1");
1069             }
1070             return digest.digest(inputBytes);
1071         } catch (Exception e) {
1072             throw new WSSecurityException(
1073                 "Error in generating digest", e
1074             );
1075         }
1076     }
1077     
1078     /**
1079      * Check that all of the QName[] requiredParts are protected by a specified action in the
1080      * results list.
1081      * @param results The List of WSSecurityEngineResults from processing
1082      * @param action The action that is required (e.g. WSConstants.SIGN)
1083      * @param requiredParts An array of QNames that correspond to the required elements
1084      */
1085     @SuppressWarnings("unchecked")
1086     public static void checkAllElementsProtected(
1087         List<WSSecurityEngineResult> results,
1088         int action,
1089         QName[] requiredParts
1090     ) throws WSSecurityException {
1091         
1092         if (requiredParts != null) {
1093             for (int i = 0; i < requiredParts.length; i++) {
1094                 QName requiredPart = requiredParts[i];
1095                 
1096                 boolean found = false;
1097                 for (Iterator<WSSecurityEngineResult> iter = results.iterator(); 
1098                     iter.hasNext() && !found;) {
1099                     WSSecurityEngineResult result = iter.next();
1100                     int resultAction = 
1101                         ((java.lang.Integer)result.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
1102                     if (resultAction != action) {
1103                         continue;
1104                     }
1105                     List<WSDataRef> refList = 
1106                         (List<WSDataRef>)result.get(WSSecurityEngineResult.TAG_DATA_REF_URIS);
1107                     if (refList != null) {
1108                         for (WSDataRef dataRef : refList) {
1109                             if (dataRef.getName().equals(requiredPart)) {
1110                                 found = true;
1111                                 break;
1112                             }
1113                         }
1114                     }
1115                 }
1116                 if (!found) {
1117                     throw new WSSecurityException(
1118                         WSSecurityException.FAILED_CHECK,
1119                         "requiredElementNotProtected",
1120                         new Object[] {requiredPart}
1121                     );
1122                 }
1123             }
1124             log.debug("All required elements are protected");
1125         }
1126     }
1127 
1128     /**
1129      * Ensure that this covers all required elements (identified by
1130      * their wsu:Id attributes).
1131      * 
1132      * @param resultItem the signature to check
1133      * @param requiredIDs the list of wsu:Id values that must be covered
1134      * @throws WSSecurityException if any required element is not included
1135      */
1136     @SuppressWarnings("unchecked")
1137     public static void checkSignsAllElements(
1138         WSSecurityEngineResult resultItem, 
1139         String[] requiredIDs
1140     ) throws WSSecurityException {
1141         int resultAction = 
1142             ((java.lang.Integer)resultItem.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
1143         if (resultAction != WSConstants.SIGN) {
1144             throw new IllegalArgumentException("Not a SIGN result");
1145         }
1146 
1147         List<WSDataRef> signedElemsRefList = 
1148             (List<WSDataRef>)resultItem.get(WSSecurityEngineResult.TAG_DATA_REF_URIS);
1149         if (signedElemsRefList == null) {
1150             throw new WSSecurityException(
1151                 "WSSecurityEngineResult does not contain any references to signed elements"
1152             );
1153         }
1154 
1155         log.debug("Checking required elements are in the signature...");
1156         for (int i = 0; i < requiredIDs.length; i++) {
1157             boolean found = false;
1158             for (int j = 0; j < signedElemsRefList.size(); j++) {
1159                 WSDataRef dataRef = (WSDataRef)signedElemsRefList.get(j);
1160                 String wsuId = dataRef.getWsuId();
1161                 if (wsuId.charAt(0) == '#') {
1162                     wsuId = wsuId.substring(1);
1163                 }
1164                 if (wsuId.equals(requiredIDs[i])) {
1165                     found = true;
1166                 }
1167             }
1168             if (!found) {
1169                 throw new WSSecurityException(
1170                     WSSecurityException.FAILED_CHECK,
1171                     "requiredElementNotSigned",
1172                     new Object[] {requiredIDs[i]}
1173                 );
1174             }
1175             log.debug("Element with ID " + requiredIDs[i] + " was correctly signed");
1176         }
1177         log.debug("All required elements are signed");
1178     }
1179     
1180     
1181     /**
1182      * @return  a list of child Nodes
1183      */
1184     public static List<Node>
1185     listChildren(
1186         final Node parent
1187     ) {
1188         final List<Node> ret = new ArrayList<Node>();
1189         if (parent != null) {
1190             Node node = parent.getFirstChild();
1191             while (node != null) {
1192                 ret.add(node);
1193                 node = node.getNextSibling();
1194             }
1195         }
1196         return ret;
1197     }
1198     
1199     /**
1200      * @return a list of Nodes in b that are not in a 
1201      */
1202     public static List<Node>
1203     newNodes(
1204         final List<Node> a,
1205         final List<Node> b
1206     ) {
1207         if (a.size() == 0) {
1208             return b;
1209         }
1210         final List<Node> ret = new ArrayList<Node>();
1211         if (b.size() == 0) {
1212             return ret;
1213         }
1214         for (
1215             final Iterator<Node> bpos = b.iterator();
1216             bpos.hasNext();
1217         ) {
1218             final Node bnode = bpos.next();
1219             final String bns = bnode.getNamespaceURI();
1220             final String bln = bnode.getLocalName();
1221             boolean found = false;
1222             for (
1223                 final Iterator<Node> apos = a.iterator();
1224                 apos.hasNext() && !found;
1225             ) {
1226                 final Node anode = apos.next();
1227                 final String ans = anode.getNamespaceURI();
1228                 final String aln = anode.getLocalName();
1229                 final boolean nsmatch =
1230                     ans == null
1231                     ? ((bns == null) ? true : false)
1232                     : ((bns == null) ? false : ans.equals(bns));
1233                 final boolean lnmatch =
1234                     aln == null
1235                     ? ((bln == null) ? true : false)
1236                     : ((bln == null) ? false : aln.equals(bln));
1237                 if (nsmatch && lnmatch) {
1238                     found = true;
1239                 }
1240             }
1241             if (!found) {
1242                 ret.add(bnode);
1243             }
1244         }
1245         return ret;
1246     }
1247     
1248     /**
1249      * Store the element argument in the DOM Crypto Context if it has one of the standard
1250      * "Id" attributes that matches the given uri
1251      */
1252     public static void storeElementInContext(
1253         DOMCryptoContext context, 
1254         String uri,
1255         Element element
1256     ) {
1257         String id = uri;
1258         if (uri.charAt(0) == '#') {
1259             id = id.substring(1);
1260         }
1261         
1262         if (element.hasAttributeNS(WSConstants.WSU_NS, "Id")
1263             && id.equals(element.getAttributeNS(WSConstants.WSU_NS, "Id"))) {
1264             context.setIdAttributeNS(element, WSConstants.WSU_NS, "Id");
1265         }
1266         if (element.hasAttributeNS(null, "Id")
1267             && id.equals(element.getAttributeNS(null, "Id"))) {
1268     	    context.setIdAttributeNS(element, null, "Id");
1269         }
1270         if (element.hasAttributeNS(null, "ID")
1271             && id.equals(element.getAttributeNS(null, "ID"))) {
1272             context.setIdAttributeNS(element, null, "ID");
1273         }
1274         if (element.hasAttributeNS(null, "AssertionID")
1275             && id.equals(element.getAttributeNS(null, "AssertionID"))) {
1276             context.setIdAttributeNS(element, null, "AssertionID");
1277         }
1278     }
1279     
1280     /**
1281      * Store the element argument in the DOM Crypto Context if it has one of the standard
1282      * "Id" attributes.
1283      */
1284     public static void storeElementInContext(
1285         DOMCryptoContext context, 
1286         Element element
1287     ) {
1288         if (element.hasAttributeNS(WSConstants.WSU_NS, "Id")) {
1289             context.setIdAttributeNS(element, WSConstants.WSU_NS, "Id");
1290         }
1291         if (element.hasAttributeNS(null, "Id")) {
1292             context.setIdAttributeNS(element, null, "Id");
1293         }
1294         if (element.hasAttributeNS(null, "ID")) {
1295             context.setIdAttributeNS(element, null, "ID");
1296         }
1297         if (element.hasAttributeNS(null, "AssertionID")) {
1298             context.setIdAttributeNS(element, null, "AssertionID");
1299         }
1300     }
1301     
1302     public static void verifySignedElement(Element elem, Document doc, Element securityHeader)
1303         throws WSSecurityException {
1304         final Element envelope = doc.getDocumentElement();
1305         final Set<String> signatureRefIDs = getSignatureReferenceIDs(securityHeader);
1306         if (!signatureRefIDs.isEmpty()) {
1307             Node cur = elem;
1308             while (!cur.isSameNode(envelope)) {
1309                 if (cur.getNodeType() == Node.ELEMENT_NODE) {
1310                     if (WSConstants.SIG_LN.equals(cur.getLocalName())
1311                         && WSConstants.SIG_NS.equals(cur.getNamespaceURI())) {
1312                         throw new WSSecurityException(WSSecurityException.FAILED_CHECK,
1313                             "requiredElementNotSigned", new Object[] {elem});
1314                     } else if (isLinkedBySignatureRefs((Element)cur, signatureRefIDs)) {
1315                         return;
1316                     }
1317                 }
1318                 cur = cur.getParentNode();
1319             }
1320         }
1321         throw new WSSecurityException(
1322             WSSecurityException.FAILED_CHECK, "requiredElementNotSigned", new Object[] {elem});
1323     }
1324     
1325     private static boolean isLinkedBySignatureRefs(Element elem, Set<String> allIDs) {
1326         // Try the wsu:Id first
1327         String attributeNS = elem.getAttributeNS(WSConstants.WSU_NS, "Id");
1328         if (!"".equals(attributeNS) && allIDs.contains(attributeNS)) {
1329             return true;
1330         }
1331         attributeNS = elem.getAttributeNS(null, "Id");
1332         return (!"".equals(attributeNS) && allIDs.contains(attributeNS));
1333     }
1334     
1335     private static Set<String> getSignatureReferenceIDs(Element wsseHeader) throws WSSecurityException {
1336         final Set<String> refs = new HashSet<String>();
1337         final List<Element> signatures = WSSecurityUtil.getDirectChildElements(wsseHeader, WSConstants.SIG_LN, WSConstants.SIG_NS);
1338         for (Element signature : signatures) {
1339             Element sigInfo = WSSecurityUtil.getDirectChildElement(signature, WSConstants.SIG_INFO_LN, WSConstants.SIG_NS);
1340             List<Element> references = WSSecurityUtil.getDirectChildElements(sigInfo, WSConstants.REF_LN, WSConstants.SIG_NS);
1341             for (Element reference : references) {
1342                 String uri = reference.getAttributeNS(null, "URI");
1343                 if (!"".equals(uri)) {
1344                     boolean added = refs.add(WSSecurityUtil.getIDFromReference(uri));
1345                     if (!added) {
1346                         log.warn("Duplicated reference uri: " + uri);
1347                     }
1348                 }
1349             }
1350         }
1351         return refs;
1352     }
1353     
1354 }