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.wss4j.dom.util;
21  
22  import org.apache.wss4j.dom.SOAP11Constants;
23  import org.apache.wss4j.dom.SOAP12Constants;
24  import org.apache.wss4j.dom.SOAPConstants;
25  import org.apache.wss4j.dom.WSConstants;
26  import org.apache.wss4j.dom.callback.CallbackLookup;
27  import org.apache.wss4j.dom.engine.WSSConfig;
28  import org.apache.wss4j.common.WSEncryptionPart;
29  import org.apache.wss4j.common.ext.WSSecurityException;
30  import org.apache.wss4j.common.util.AttachmentUtils;
31  import org.apache.wss4j.common.util.XMLUtils;
32  import org.apache.wss4j.dom.handler.HandlerAction;
33  import org.apache.wss4j.dom.handler.RequestData;
34  import org.apache.wss4j.dom.handler.WSHandlerConstants;
35  import org.w3c.dom.Attr;
36  import org.w3c.dom.Document;
37  import org.w3c.dom.Element;
38  import org.w3c.dom.Node;
39  import org.w3c.dom.Text;
40  
41  //import com.sun.xml.internal.messaging.saaj.soap.SOAPDocumentImpl;
42  
43  import java.lang.reflect.AccessibleObject;
44  import java.lang.reflect.InvocationTargetException;
45  import java.lang.reflect.Method;
46  import java.security.AccessController;
47  import java.security.PrivilegedAction;
48  import java.security.PrivilegedActionException;
49  import java.security.PrivilegedExceptionAction;
50  import java.util.ArrayList;
51  import java.util.Collections;
52  import java.util.List;
53  
54  import javax.security.auth.callback.CallbackHandler;
55  
56  
57  /**
58   * WS-Security Utility methods. <p/>
59   */
60  public final class WSSecurityUtil {
61  
62      private static boolean isSAAJ14 = false;
63  
64      private static final org.slf4j.Logger LOG =
65          org.slf4j.LoggerFactory.getLogger(WSSecurityUtil.class);
66  
67      private static final ClassValue<Method> GET_DOM_ELEMENTS_METHODS = new ClassValue<Method>() {
68          @Override
69          protected Method computeValue(Class<?> type) {
70              try {
71                  return getMethod(type, "getDomElement");
72              } catch (NoSuchMethodException e) {
73                  //best effort to try, do nothing if NoSuchMethodException
74                  return null;
75              }
76          }
77      };
78  
79      private static final ClassValue<Method> GET_ENVELOPE_METHODS = new ClassValue<Method>() {
80          @Override
81          protected Method computeValue(Class<?> type) {
82              try {
83                  return getMethod(type, "getEnvelope");
84              } catch (NoSuchMethodException e) {
85                  //best effort to try, do nothing if NoSuchMethodException
86                  return null;
87              }
88          }
89      };
90  
91      static {
92          try {
93              Method[] methods = WSSecurityUtil.class.getClassLoader().
94                  loadClass("com.sun.xml.messaging.saaj.soap.SOAPDocumentImpl").getMethods();
95              for (Method method : methods) {
96                  if (method.getName().equals("register")) {
97                      //this is the 1.4+ SAAJ impl
98                      isSAAJ14 = true;
99                      break;
100                 }
101             }
102         } catch (ClassNotFoundException cnfe) {
103             LOG.debug("Can't load class com.sun.xml.messaging.saaj.soap.SOAPDocumentImpl", cnfe);
104 
105             try {
106                 Method[] methods = WSSecurityUtil.class.getClassLoader().
107                     loadClass("com.sun.xml.internal.messaging.saaj.soap.SOAPDocumentImpl").getMethods();
108                 for (Method method : methods) {
109                     if (method.getName().equals("register")) {
110                         //this is the SAAJ impl in JDK9
111                         isSAAJ14 = true;
112                         break;
113                     }
114                 }
115             } catch (ClassNotFoundException cnfe1) {
116                 LOG.debug("can't load class com.sun.xml.internal.messaging.saaj.soap.SOAPDocumentImpl", cnfe1);
117             }
118         }
119     }
120 
121     private WSSecurityUtil() {
122         // Complete
123     }
124 
125     private static Method getMethod(final Class<?> clazz, final String name,
126                                    final Class<?>... parameterTypes) throws NoSuchMethodException {
127         try {
128             return AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
129                 public Method run() throws Exception {
130                     return clazz.getMethod(name, parameterTypes);
131                 }
132             });
133         } catch (PrivilegedActionException pae) {
134             Exception e = pae.getException();
135             if (e instanceof NoSuchMethodException) {
136                 throw (NoSuchMethodException)e;
137             }
138             throw new SecurityException(e);
139         }
140     }
141 
142     private static <T extends AccessibleObject> T setAccessible(final T o) {
143         return AccessController.doPrivileged(new PrivilegedAction<T>() {
144             public T run() {
145                 o.setAccessible(true);
146                 return o;
147             }
148         });
149     }
150 
151     public static Element getSOAPHeader(Document doc) {
152         String soapNamespace = WSSecurityUtil.getSOAPNamespace(doc.getDocumentElement());
153         return
154             XMLUtils.getDirectChildElement(
155                 doc.getDocumentElement(), WSConstants.ELEM_HEADER, soapNamespace
156             );
157     }
158 
159     /**
160      * Returns the first WS-Security header element for a given actor. Only one
161      * WS-Security header is allowed for an actor.
162      *
163      * @param doc
164      * @param actor
165      * @return the <code>wsse:Security</code> element or <code>null</code>
166      *         if not such element found
167      */
168     public static Element getSecurityHeader(Document doc, String actor) throws WSSecurityException {
169         Element soapHeaderElement = getSOAPHeader(doc);
170         if (soapHeaderElement == null) { // no SOAP header at all
171             return null;
172         }
173 
174         String soapNamespace = WSSecurityUtil.getSOAPNamespace(doc.getDocumentElement());
175         return getSecurityHeader(soapHeaderElement, actor, WSConstants.URI_SOAP12_ENV.equals(soapNamespace));
176     }
177 
178     /**
179      * Returns the first WS-Security header element for a given actor. Only one
180      * WS-Security header is allowed for an actor.
181      */
182     public static Element getSecurityHeader(Element soapHeader, String actor, boolean soap12)
183         throws WSSecurityException {
184 
185         String actorLocal = WSConstants.ATTR_ACTOR;
186         String soapNamespace = WSConstants.URI_SOAP11_ENV;
187         if (soap12) {
188             actorLocal = WSConstants.ATTR_ROLE;
189             soapNamespace = WSConstants.URI_SOAP12_ENV;
190         }
191 
192         //
193         // Iterate through the security headers
194         //
195         Element foundSecurityHeader = null;
196         for (
197             Node currentChild = soapHeader.getFirstChild();
198             currentChild != null;
199             currentChild = currentChild.getNextSibling()
200         ) {
201             if (Node.ELEMENT_NODE == currentChild.getNodeType()
202                 && WSConstants.WSSE_LN.equals(currentChild.getLocalName())
203                 && (WSConstants.WSSE_NS.equals(currentChild.getNamespaceURI())
204                     || WSConstants.OLD_WSSE_NS.equals(currentChild.getNamespaceURI()))) {
205 
206                 Element elem = (Element)currentChild;
207                 Attr attr = elem.getAttributeNodeNS(soapNamespace, actorLocal);
208                 String hActor = (attr != null) ? attr.getValue() : null;
209 
210                 if (WSSecurityUtil.isActorEqual(actor, hActor)) {
211                     if (foundSecurityHeader != null) {
212                         LOG.debug(
213                             "Two or more security headers have the same actor name: {}", actor
214                         );
215                         throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
216                     }
217                     foundSecurityHeader = elem;
218                 }
219             }
220         }
221         return foundSecurityHeader;
222     }
223 
224 
225     /**
226      * Compares two actor strings and returns true if these are equal. Takes
227      * care of the null length strings and uses ignore case.
228      *
229      * @param actor
230      * @param hActor
231      * @return true is the actor arguments are equal
232      */
233     public static boolean isActorEqual(String actor, String hActor) {
234         if ((hActor == null || hActor.length() == 0)
235             && (actor == null || actor.length() == 0)) {
236             return true;
237         }
238 
239         return hActor != null && actor != null && hActor.equalsIgnoreCase(actor);
240     }
241 
242     /**
243      * Gets all direct children with specified localname and namespace. <p/>
244      *
245      * @param fNode the node where to start the search
246      * @param localName local name of the children to get
247      * @param namespace the namespace of the children to get
248      * @return the list of nodes or <code>null</code> if not such nodes are found
249      */
250     public static List<Element> getDirectChildElements(
251         Node fNode,
252         String localName,
253         String namespace
254     ) {
255         List<Element> children = new ArrayList<>();
256         for (
257             Node currentChild = fNode.getFirstChild();
258             currentChild != null;
259             currentChild = currentChild.getNextSibling()
260         ) {
261             if (Node.ELEMENT_NODE == currentChild.getNodeType()
262                 && localName.equals(currentChild.getLocalName())
263                 && namespace.equals(currentChild.getNamespaceURI())) {
264                 children.add((Element)currentChild);
265             }
266         }
267         return children;
268     }
269 
270 
271     /**
272      * return the first soap "Body" element. <p/>
273      *
274      * @param doc
275      * @return the body element or <code>null</code> if document does not
276      *         contain a SOAP body
277      */
278     public static Element findBodyElement(Document doc) {
279         Element docElement = doc.getDocumentElement();
280         String ns = docElement.getNamespaceURI();
281         return XMLUtils.getDirectChildElement(docElement, WSConstants.ELEM_BODY, ns);
282     }
283 
284 
285     /**
286      * Find the DOM Element in the SOAP Envelope that is referenced by the
287      * WSEncryptionPart argument. The "Id" is used before the Element localname/namespace.
288      *
289      * @param part The WSEncryptionPart object corresponding to the DOM Element(s) we want
290      * @param callbackLookup The CallbackLookup object used to find Elements
291      * @return the DOM Element in the SOAP Envelope that is found
292      */
293     public static List<Element> findElements(
294         WSEncryptionPart part, CallbackLookup callbackLookup
295     ) throws WSSecurityException {
296         // See if the DOM Element is stored in the WSEncryptionPart first
297         if (part.getElement() != null) {
298             return Collections.singletonList(part.getElement());
299         }
300 
301         // Next try to find the Element via its wsu:Id
302         String id = part.getId();
303         if (id != null) {
304             Element foundElement = callbackLookup.getElement(id, null, false);
305             return Collections.singletonList(foundElement);
306         }
307         // Otherwise just lookup all elements with the localname/namespace
308         return callbackLookup.getElements(part.getName(), part.getNamespace());
309     }
310 
311 
312 
313     /**
314      * Get the default encryption part - the SOAP Body of type "Content".
315      */
316     public static WSEncryptionPart getDefaultEncryptionPart(Document doc) {
317         String soapNamespace =
318             WSSecurityUtil.getSOAPNamespace(doc.getDocumentElement());
319         return new WSEncryptionPart(WSConstants.ELEM_BODY, soapNamespace, "Content");
320     }
321 
322     /**
323      * create a new element in the same namespace <p/>
324      *
325      * @param parent for the new element
326      * @param localName of the new element
327      * @return the new element
328      */
329     private static Element createElementInSameNamespace(Node parent, String localName) {
330         String qName = localName;
331         String prefix = parent.getPrefix();
332         if (prefix != null && prefix.length() > 0) {
333             qName = prefix + ":" + localName;
334         }
335 
336         String nsUri = parent.getNamespaceURI();
337         return parent.getOwnerDocument().createElementNS(nsUri, qName);
338     }
339 
340 
341 
342 
343     /**
344      * prepend a child element <p/>
345      *
346      * @param parent element of this child element
347      * @param child the element to append
348      * @return the child element
349      */
350     public static Element prependChildElement(
351         Element parent,
352         Element child
353     ) {
354         Node firstChild = parent.getFirstChild();
355         Element domChild = null;
356         try {
357             domChild = (Element)getDomElement(child);
358         } catch (WSSecurityException e) {
359             LOG.debug("Error when try to get Dom Element from the child", e);
360         }
361         if (firstChild == null) {
362             return (Element)parent.appendChild(domChild);
363         } else {
364             return (Element)parent.insertBefore(domChild, firstChild);
365         }
366     }
367 
368 
369     /**
370      * find the first ws-security header block <p/>
371      *
372      * @param doc the DOM document (SOAP request)
373      * @param envelope the SOAP envelope
374      * @param doCreate if true create a new WSS header block if none exists
375      * @return the WSS header or null if none found and doCreate is false
376      */
377     public static Element findWsseSecurityHeaderBlock(
378         Document doc,
379         Element envelope,
380         boolean doCreate
381     ) throws WSSecurityException {
382         return findWsseSecurityHeaderBlock(doc, envelope, null, doCreate);
383     }
384 
385     /**
386      * find a WS-Security header block for a given actor <p/>
387      *
388      * @param doc the DOM document (SOAP request)
389      * @param envelope the SOAP envelope
390      * @param actor the actor (role) name of the WSS header
391      * @param doCreate if true create a new WSS header block if none exists
392      * @return the WSS header or null if none found and doCreate is false
393      */
394     public static Element findWsseSecurityHeaderBlock(
395         Document doc,
396         Element envelope,
397         String actor,
398         boolean doCreate
399     ) throws WSSecurityException {
400         String soapNamespace = WSSecurityUtil.getSOAPNamespace(doc.getDocumentElement());
401         Element header =
402             XMLUtils.getDirectChildElement(
403                 doc.getDocumentElement(),
404                 WSConstants.ELEM_HEADER,
405                 soapNamespace
406             );
407         if (header == null) { // no SOAP header at all
408             if (doCreate) {
409                 if (isSAAJ14) {
410                     try {
411                         Node node = null;
412                         Method method = GET_ENVELOPE_METHODS.get(doc.getClass());
413                         if (method != null) {
414                             try {
415                                 node = (Node)setAccessible(method).invoke(doc);
416                             } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
417                                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
418                             }
419                         }
420                         if (node != null) {
421                             header = createElementInSameNamespace(node, WSConstants.ELEM_HEADER);
422                         } else {
423                             header = createElementInSameNamespace(doc.getDocumentElement(), WSConstants.ELEM_HEADER);
424                         }
425                         header = (Element)doc.importNode(header, true);
426                         header = (Element)getDomElement(header);
427                         header = prependChildElement(envelope, header);
428 
429                     } catch (Exception e) {
430                         throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
431                     }
432 
433                 } else {
434                     header = createElementInSameNamespace(envelope, WSConstants.ELEM_HEADER);
435                     header = prependChildElement(envelope, header);
436                 }
437             } else {
438                 return null;
439             }
440         }
441 
442         String actorLocal = WSConstants.ATTR_ACTOR;
443         if (WSConstants.URI_SOAP12_ENV.equals(soapNamespace)) {
444             actorLocal = WSConstants.ATTR_ROLE;
445         }
446 
447         //
448         // Iterate through the security headers
449         //
450         Element foundSecurityHeader = null;
451         for (
452             Node currentChild = header.getFirstChild();
453             currentChild != null;
454             currentChild = currentChild.getNextSibling()
455         ) {
456             if (Node.ELEMENT_NODE == currentChild.getNodeType()
457                 && WSConstants.WSSE_LN.equals(currentChild.getLocalName())
458                 && WSConstants.WSSE_NS.equals(currentChild.getNamespaceURI())) {
459 
460                 Element elem = (Element)currentChild;
461                 Attr attr = elem.getAttributeNodeNS(soapNamespace, actorLocal);
462                 String hActor = (attr != null) ? attr.getValue() : null;
463 
464                 if (WSSecurityUtil.isActorEqual(actor, hActor)) {
465                     if (foundSecurityHeader != null) {
466                         LOG.debug(
467                             "Two or more security headers have the same actor name: {}", actor
468                         );
469                         throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
470                     }
471                     foundSecurityHeader = elem;
472                 }
473             }
474         }
475         if (foundSecurityHeader != null) {
476             return foundSecurityHeader;
477         } else if (doCreate) {
478             foundSecurityHeader = doc.createElementNS(WSConstants.WSSE_NS, "wsse:Security");
479             foundSecurityHeader.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:wsse", WSConstants.WSSE_NS);
480             foundSecurityHeader = (Element)doc.importNode(foundSecurityHeader, true);
481             foundSecurityHeader = (Element)getDomElement(foundSecurityHeader);
482 
483             return prependChildElement(header, foundSecurityHeader);
484         }
485         return null;
486     }
487 
488     /**
489      * create a base64 test node <p/>
490      *
491      * @param doc the DOM document (SOAP request)
492      * @param data to encode
493      * @return a Text node containing the base64 encoded data
494      */
495     public static Text createBase64EncodedTextNode(Document doc, byte[] data) {
496         return doc.createTextNode(org.apache.xml.security.utils.XMLUtils.encodeToString(data));
497     }
498 
499     public static SOAPConstants getSOAPConstants(Element startElement) {
500         Document doc = startElement.getOwnerDocument();
501         String ns = doc.getDocumentElement().getNamespaceURI();
502         if (WSConstants.URI_SOAP12_ENV.equals(ns)) {
503             return new SOAP12Constants();
504         }
505         return new SOAP11Constants();
506     }
507 
508     public static String getSOAPNamespace(Element startElement) {
509         return getSOAPConstants(startElement).getEnvelopeURI();
510     }
511 
512     public static List<Integer> decodeAction(String action) throws WSSecurityException {
513         String actionToParse = action;
514         if (actionToParse == null) {
515             return Collections.emptyList();
516         }
517         actionToParse = actionToParse.trim();
518         if (actionToParse.length() == 0) {
519             return Collections.emptyList();
520         }
521 
522         List<Integer> actions = new ArrayList<>();
523         String[] single = actionToParse.split("\\s");
524         for (String parsedAction : single) {
525             if (parsedAction.equals(WSHandlerConstants.NO_SECURITY)) {
526                 return Collections.emptyList();
527             } else if (parsedAction.equals(WSHandlerConstants.USERNAME_TOKEN)) {
528                 actions.add(WSConstants.UT);
529             } else if (parsedAction.equals(WSHandlerConstants.USERNAME_TOKEN_NO_PASSWORD)) {
530                 actions.add(WSConstants.UT_NOPASSWORD);
531             } else if (parsedAction.equals(WSHandlerConstants.SIGNATURE)) {
532                 actions.add(WSConstants.SIGN);
533             } else if (parsedAction.equals(WSHandlerConstants.SIGNATURE_DERIVED)) {
534                 actions.add(WSConstants.DKT_SIGN);
535             } else if (parsedAction.equals(WSHandlerConstants.ENCRYPT)
536                 || parsedAction.equals(WSHandlerConstants.ENCRYPTION)) {
537                 actions.add(WSConstants.ENCR);
538             } else if (parsedAction.equals(WSHandlerConstants.ENCRYPT_DERIVED)
539                 || parsedAction.equals(WSHandlerConstants.ENCRYPTION_DERIVED)) {
540                 actions.add(WSConstants.DKT_ENCR);
541             } else if (parsedAction.equals(WSHandlerConstants.SAML_TOKEN_UNSIGNED)) {
542                 actions.add(WSConstants.ST_UNSIGNED);
543             } else if (parsedAction.equals(WSHandlerConstants.SAML_TOKEN_SIGNED)) {
544                 actions.add(WSConstants.ST_SIGNED);
545             } else if (parsedAction.equals(WSHandlerConstants.TIMESTAMP)) {
546                 actions.add(WSConstants.TS);
547             } else if (parsedAction.equals(WSHandlerConstants.USERNAME_TOKEN_SIGNATURE)) {
548                 actions.add(WSConstants.UT_SIGN);
549             } else if (parsedAction.equals(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION)) {
550                 actions.add(WSConstants.SC);
551             } else if (parsedAction.equals(WSHandlerConstants.CUSTOM_TOKEN)) {
552                 actions.add(WSConstants.CUSTOM_TOKEN);
553             } else {
554                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
555                                               new Object[] {"Unknown action defined: " + parsedAction}
556                 );
557             }
558         }
559         return actions;
560     }
561 
562 
563     /**
564      * Decode an action String. This method should only be called on the outbound side.
565      * @param action The initial String of actions to perform
566      * @param wssConfig This object holds the list of custom actions to be performed.
567      * @return The list of HandlerAction Objects
568      * @throws WSSecurityException
569      */
570     public static List<HandlerAction> decodeHandlerAction(
571         String action,
572         WSSConfig wssConfig
573     ) throws WSSecurityException {
574         if (action == null) {
575             return Collections.emptyList();
576         }
577 
578         List<HandlerAction> actions = new ArrayList<>();
579         String[] single = action.split(" ");
580         for (String parsedAction : single) {
581             if (parsedAction.equals(WSHandlerConstants.NO_SECURITY)) {
582                 return actions;
583             } else if (parsedAction.equals(WSHandlerConstants.USERNAME_TOKEN)) {
584                 actions.add(new HandlerAction(WSConstants.UT));
585             } else if (parsedAction.equals(WSHandlerConstants.USERNAME_TOKEN_NO_PASSWORD)) {
586                 actions.add(new HandlerAction(WSConstants.UT_NOPASSWORD));
587             } else if (parsedAction.equals(WSHandlerConstants.SIGNATURE)) {
588                 actions.add(new HandlerAction(WSConstants.SIGN));
589             } else if (parsedAction.equals(WSHandlerConstants.SIGNATURE_DERIVED)) {
590                 actions.add(new HandlerAction(WSConstants.DKT_SIGN));
591             } else if (parsedAction.equals(WSHandlerConstants.ENCRYPT)
592                 || parsedAction.equals(WSHandlerConstants.ENCRYPTION)) {
593                 actions.add(new HandlerAction(WSConstants.ENCR));
594             } else if (parsedAction.equals(WSHandlerConstants.ENCRYPT_DERIVED)
595                 || parsedAction.equals(WSHandlerConstants.ENCRYPTION_DERIVED)) {
596                 actions.add(new HandlerAction(WSConstants.DKT_ENCR));
597             } else if (parsedAction.equals(WSHandlerConstants.SAML_TOKEN_UNSIGNED)) {
598                 actions.add(new HandlerAction(WSConstants.ST_UNSIGNED));
599             } else if (parsedAction.equals(WSHandlerConstants.SAML_TOKEN_SIGNED)) {
600                 actions.add(new HandlerAction(WSConstants.ST_SIGNED));
601             } else if (parsedAction.equals(WSHandlerConstants.TIMESTAMP)) {
602                 actions.add(new HandlerAction(WSConstants.TS));
603             } else if (parsedAction.equals(WSHandlerConstants.USERNAME_TOKEN_SIGNATURE)) {
604                 actions.add(new HandlerAction(WSConstants.UT_SIGN));
605             } else if (parsedAction.equals(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION)) {
606                 actions.add(new HandlerAction(WSConstants.SC));
607             } else if (parsedAction.equals(WSHandlerConstants.CUSTOM_TOKEN)) {
608                 actions.add(new HandlerAction(WSConstants.CUSTOM_TOKEN));
609             } else {
610                 try {
611                     int customAction = Integer.parseInt(parsedAction);
612                     if (wssConfig == null || wssConfig.getAction(customAction) == null) {
613                         throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
614                                                       new Object[] {"Unknown action defined: " + parsedAction}
615                         );
616                     }
617                     actions.add(new HandlerAction(customAction));
618                 } catch (NumberFormatException ex) {
619                     throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
620                                                   new Object[] {"Unknown action defined: " + parsedAction}
621                     );
622                 }
623             }
624         }
625         return actions;
626     }
627 
628     public static void inlineAttachments(List<Element> includeElements,
629                                          CallbackHandler attachmentCallbackHandler,
630                                          boolean removeAttachments) throws WSSecurityException {
631         for (Element includeElement : includeElements) {
632             String xopURI = includeElement.getAttributeNS(null, "href");
633             if (xopURI != null) {
634                 // Retrieve the attachment bytes
635                 byte[] attachmentBytes =
636                     WSSecurityUtil.getBytesFromAttachment(xopURI, attachmentCallbackHandler, removeAttachments);
637                 String encodedBytes = org.apache.xml.security.utils.XMLUtils.encodeToString(attachmentBytes);
638 
639                 Node encodedChild =
640                     includeElement.getOwnerDocument().createTextNode(encodedBytes);
641                 includeElement.getParentNode().replaceChild(encodedChild, includeElement);
642             }
643         }
644     }
645 
646     /**
647      * Register the jakarta.xml.soap.Node with new Cloned Dom Node with java9
648      * @param doc The SOAPDocumentImpl
649      * @param clonedElement The cloned Element
650      * @return new clonedElement which already associated with the SAAJ Node
651      * @throws WSSecurityException
652      */
653     public static Element cloneElement(Document doc, Element clonedElement) throws WSSecurityException {
654         clonedElement = (Element)clonedElement.cloneNode(true);
655         if (isSAAJ14) {
656             // here we need register the jakarta.xml.soap.Node with new instance
657             clonedElement = (Element)doc.importNode(clonedElement, true);
658             clonedElement = (Element)getDomElement(clonedElement);
659         }
660         return clonedElement;
661     }
662 
663     /**
664      * Try to get the DOM Node from the SAAJ Node with JAVA9
665      * @param node The original node we need check
666      * @return The DOM node
667      * @throws WSSecurityException
668      */
669     private static Node getDomElement(Node node) throws WSSecurityException {
670         if (node != null && isSAAJ14) {
671 
672             Method method = GET_DOM_ELEMENTS_METHODS.get(node.getClass());
673             if (method != null) {
674                 try {
675                     return (Node)setAccessible(method).invoke(node);
676                 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
677                     throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
678                 }
679             }
680         }
681         return node;
682     }
683 
684     public static byte[] getBytesFromAttachment(
685         String xopUri, RequestData data
686     ) throws WSSecurityException {
687         return getBytesFromAttachment(xopUri, data.getAttachmentCallbackHandler());
688     }
689 
690     public static byte[] getBytesFromAttachment(
691         String xopUri, CallbackHandler attachmentCallbackHandler
692     ) throws WSSecurityException {
693         return getBytesFromAttachment(xopUri, attachmentCallbackHandler, true);
694     }
695 
696     public static byte[] getBytesFromAttachment(
697         String xopUri, CallbackHandler attachmentCallbackHandler, boolean removeAttachments
698     ) throws WSSecurityException {
699         return AttachmentUtils.getBytesFromAttachment(xopUri, attachmentCallbackHandler, removeAttachments);
700     }
701 
702     public static String getAttachmentId(String xopUri) throws WSSecurityException {
703         return AttachmentUtils.getAttachmentId(xopUri);
704     }
705 
706 }