1 /*
2  * reserved comment block
3  * DO NOT REMOVE OR ALTER!
4  */
5 /**
6  * Licensed to the Apache Software Foundation (ASF) under one
7  * or more contributor license agreements. See the NOTICE file
8  * distributed with this work for additional information
9  * regarding copyright ownership. The ASF licenses this file
10  * to you under the Apache License, Version 2.0 (the
11  * "License"); you may not use this file except in compliance
12  * with the License. You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing,
17  * software distributed under the License is distributed on an
18  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19  * KIND, either express or implied. See the License for the
20  * specific language governing permissions and limitations
21  * under the License.
22  */
23 package com.sun.org.apache.xml.internal.security.utils;
24 
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.lang.reflect.InvocationTargetException;
29 import java.math.BigInteger;
30 import java.security.AccessController;
31 import java.security.PrivilegedAction;
32 import java.util.ArrayList;
33 import java.util.Base64;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Set;
37 import java.util.stream.Collectors;
38 
39 import com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException;
40 import com.sun.org.apache.xml.internal.security.c14n.Canonicalizer;
41 import com.sun.org.apache.xml.internal.security.c14n.InvalidCanonicalizerException;
42 import com.sun.org.apache.xml.internal.security.parser.XMLParser;
43 import com.sun.org.apache.xml.internal.security.parser.XMLParserException;
44 import com.sun.org.apache.xml.internal.security.parser.XMLParserImpl;
45 import org.w3c.dom.Attr;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.Element;
48 import org.w3c.dom.NamedNodeMap;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.NodeList;
51 import org.w3c.dom.Text;
52 
53 /**
54  * DOM and XML accessibility and comfort functions.
55  *
56  */
57 public final class XMLUtils {
58 
59     private static boolean ignoreLineBreaks =
60             AccessController.doPrivileged(
61                     (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("com.sun.org.apache.xml.internal.security.ignoreLineBreaks"));
62 
63     private static final com.sun.org.slf4j.internal.Logger LOG =
64             com.sun.org.slf4j.internal.LoggerFactory.getLogger(XMLUtils.class);
65 
66     private static XMLParser xmlParserImpl =
67             AccessController.doPrivileged(
68                     (PrivilegedAction<XMLParser>) () -> {
69                         String xmlParserClass = System.getProperty("com.sun.org.apache.xml.internal.security.XMLParser");
70                         if (xmlParserClass != null) {
71                             try {
72                                 return (XMLParser) JavaUtils.newInstanceWithEmptyConstructor(
73                                         ClassLoaderUtils.loadClass(xmlParserClass, XMLUtils.class));
74                             } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
75                                 LOG.error("Error instantiating XMLParser. Falling back to XMLParserImpl");
76                             }
77                         }
78                         return new XMLParserImpl();
79                     });
80 
81     private static volatile String dsPrefix = "ds";
82     private static volatile String ds11Prefix = "dsig11";
83     private static volatile String xencPrefix = "xenc";
84     private static volatile String xenc11Prefix = "xenc11";
85 
86     /**
87      * Constructor XMLUtils
88      *
89      */
XMLUtils()90     private XMLUtils() {
91         // we don't allow instantiation
92     }
93 
94     /**
95      * Set the prefix for the digital signature namespace
96      * @param prefix the new prefix for the digital signature namespace
97      * @throws SecurityException if a security manager is installed and the
98      *    caller does not have permission to set the prefix
99      */
setDsPrefix(String prefix)100     public static void setDsPrefix(String prefix) {
101         JavaUtils.checkRegisterPermission();
102         dsPrefix = prefix;
103     }
104 
105     /**
106      * Set the prefix for the digital signature 1.1 namespace
107      * @param prefix the new prefix for the digital signature 1.1 namespace
108      * @throws SecurityException if a security manager is installed and the
109      *    caller does not have permission to set the prefix
110      */
setDs11Prefix(String prefix)111     public static void setDs11Prefix(String prefix) {
112         JavaUtils.checkRegisterPermission();
113         ds11Prefix = prefix;
114     }
115 
116     /**
117      * Set the prefix for the encryption namespace
118      * @param prefix the new prefix for the encryption namespace
119      * @throws SecurityException if a security manager is installed and the
120      *    caller does not have permission to set the prefix
121      */
setXencPrefix(String prefix)122     public static void setXencPrefix(String prefix) {
123         JavaUtils.checkRegisterPermission();
124         xencPrefix = prefix;
125     }
126 
127     /**
128      * Set the prefix for the encryption namespace 1.1
129      * @param prefix the new prefix for the encryption namespace 1.1
130      * @throws SecurityException if a security manager is installed and the
131      *    caller does not have permission to set the prefix
132      */
setXenc11Prefix(String prefix)133     public static void setXenc11Prefix(String prefix) {
134         JavaUtils.checkRegisterPermission();
135         xenc11Prefix = prefix;
136     }
137 
getNextElement(Node el)138     public static Element getNextElement(Node el) {
139         Node node = el;
140         while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
141             node = node.getNextSibling();
142         }
143         return (Element)node;
144     }
145 
146     /**
147      * @param rootNode
148      * @param result
149      * @param exclude
150      * @param com whether comments or not
151      */
getSet(Node rootNode, Set<Node> result, Node exclude, boolean com)152     public static void getSet(Node rootNode, Set<Node> result, Node exclude, boolean com) {
153         if (exclude != null && isDescendantOrSelf(exclude, rootNode)) {
154             return;
155         }
156         getSetRec(rootNode, result, exclude, com);
157     }
158 
159     @SuppressWarnings("fallthrough")
getSetRec(final Node rootNode, final Set<Node> result, final Node exclude, final boolean com)160     private static void getSetRec(final Node rootNode, final Set<Node> result,
161                                 final Node exclude, final boolean com) {
162         if (rootNode == exclude) {
163             return;
164         }
165         switch (rootNode.getNodeType()) { //NOPMD
166         case Node.ELEMENT_NODE:
167             result.add(rootNode);
168             Element el = (Element)rootNode;
169             if (el.hasAttributes()) {
170                 NamedNodeMap nl = el.getAttributes();
171                 int length = nl.getLength();
172                 for (int i = 0; i < length; i++) {
173                     result.add(nl.item(i));
174                 }
175             }
176             //no return keep working
177         case Node.DOCUMENT_NODE:
178             for (Node r = rootNode.getFirstChild(); r != null; r = r.getNextSibling()) {
179                 if (r.getNodeType() == Node.TEXT_NODE) {
180                     result.add(r);
181                     while (r != null && r.getNodeType() == Node.TEXT_NODE) {
182                         r = r.getNextSibling();
183                     }
184                     if (r == null) {
185                         return;
186                     }
187                 }
188                 getSetRec(r, result, exclude, com);
189             }
190             break;
191         case Node.COMMENT_NODE:
192             if (com) {
193                 result.add(rootNode);
194             }
195             break;
196         case Node.DOCUMENT_TYPE_NODE:
197             break;
198         default:
199             result.add(rootNode);
200         }
201     }
202 
203 
204     /**
205      * Outputs a DOM tree to an {@link OutputStream}.
206      *
207      * @param contextNode root node of the DOM tree
208      * @param os the {@link OutputStream}
209      */
outputDOM(Node contextNode, OutputStream os)210     public static void outputDOM(Node contextNode, OutputStream os) {
211         XMLUtils.outputDOM(contextNode, os, false);
212     }
213 
214     /**
215      * Outputs a DOM tree to an {@link OutputStream}. <I>If an Exception is
216      * thrown during execution, it's StackTrace is output to System.out, but the
217      * Exception is not re-thrown.</I>
218      *
219      * @param contextNode root node of the DOM tree
220      * @param os the {@link OutputStream}
221      * @param addPreamble
222      */
outputDOM(Node contextNode, OutputStream os, boolean addPreamble)223     public static void outputDOM(Node contextNode, OutputStream os, boolean addPreamble) {
224         try {
225             if (addPreamble) {
226                 os.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".getBytes(java.nio.charset.StandardCharsets.UTF_8));
227             }
228 
229             Canonicalizer.getInstance(
230                 Canonicalizer.ALGO_ID_C14N_PHYSICAL).canonicalizeSubtree(contextNode, os);
231         } catch (IOException | InvalidCanonicalizerException | CanonicalizationException ex) {
232             LOG.debug(ex.getMessage(), ex);
233         }
234     }
235 
236     /**
237      * Serializes the {@code contextNode} into the OutputStream, <I>but
238      * suppresses all Exceptions</I>.
239      * <p></p>
240      * NOTE: <I>This should only be used for debugging purposes,
241      * NOT in a production environment; this method ignores all exceptions,
242      * so you won't notice if something goes wrong. If you're asking what is to
243      * be used in a production environment, simply use the code inside the
244      * {@code try{}} statement, but handle the Exceptions appropriately.</I>
245      *
246      * @param contextNode
247      * @param os
248      */
outputDOMc14nWithComments(Node contextNode, OutputStream os)249     public static void outputDOMc14nWithComments(Node contextNode, OutputStream os) {
250         try {
251             Canonicalizer.getInstance(
252                 Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS).canonicalizeSubtree(contextNode, os);
253         } catch (InvalidCanonicalizerException | CanonicalizationException ex) {
254             LOG.debug(ex.getMessage(), ex);
255             // throw new RuntimeException(ex.getMessage());
256         }
257     }
258 
259     /**
260      * Method getFullTextChildrenFromNode
261      *
262      * @param node
263      * @return the string of children
264      */
getFullTextChildrenFromNode(Node node)265     public static String getFullTextChildrenFromNode(Node node) {
266         StringBuilder sb = new StringBuilder();
267 
268         Node child = node.getFirstChild();
269         while (child != null) {
270             if (child.getNodeType() == Node.TEXT_NODE) {
271                 sb.append(((Text)child).getData());
272             }
273             child = child.getNextSibling();
274         }
275 
276         return sb.toString();
277     }
278 
279     /**
280      * Creates an Element in the XML Signature specification namespace.
281      *
282      * @param doc the factory Document
283      * @param elementName the local name of the Element
284      * @return the Element
285      */
createElementInSignatureSpace(Document doc, String elementName)286     public static Element createElementInSignatureSpace(Document doc, String elementName) {
287         if (doc == null) {
288             throw new RuntimeException("Document is null");
289         }
290 
291         if (dsPrefix == null || dsPrefix.length() == 0) {
292             return doc.createElementNS(Constants.SignatureSpecNS, elementName);
293         }
294         return doc.createElementNS(Constants.SignatureSpecNS, dsPrefix + ":" + elementName);
295     }
296 
297     /**
298      * Creates an Element in the XML Signature 1.1 specification namespace.
299      *
300      * @param doc the factory Document
301      * @param elementName the local name of the Element
302      * @return the Element
303      */
createElementInSignature11Space(Document doc, String elementName)304     public static Element createElementInSignature11Space(Document doc, String elementName) {
305         if (doc == null) {
306             throw new RuntimeException("Document is null");
307         }
308 
309         if (ds11Prefix == null || ds11Prefix.length() == 0) {
310             return doc.createElementNS(Constants.SignatureSpec11NS, elementName);
311         }
312         return doc.createElementNS(Constants.SignatureSpec11NS, ds11Prefix + ":" + elementName);
313     }
314 
315     /**
316      * Creates an Element in the XML Encryption specification namespace.
317      *
318      * @param doc the factory Document
319      * @param elementName the local name of the Element
320      * @return the Element
321      */
createElementInEncryptionSpace(Document doc, String elementName)322     public static Element createElementInEncryptionSpace(Document doc, String elementName) {
323         if (doc == null) {
324             throw new RuntimeException("Document is null");
325         }
326 
327         if (xencPrefix == null || xencPrefix.length() == 0) {
328             return doc.createElementNS(EncryptionConstants.EncryptionSpecNS, elementName);
329         }
330         return
331             doc.createElementNS(
332                 EncryptionConstants.EncryptionSpecNS, xencPrefix + ":" + elementName
333             );
334     }
335 
336     /**
337      * Creates an Element in the XML Encryption 1.1 specification namespace.
338      *
339      * @param doc the factory Document
340      * @param elementName the local name of the Element
341      * @return the Element
342      */
createElementInEncryption11Space(Document doc, String elementName)343     public static Element createElementInEncryption11Space(Document doc, String elementName) {
344         if (doc == null) {
345             throw new RuntimeException("Document is null");
346         }
347 
348         if (xenc11Prefix == null || xenc11Prefix.length() == 0) {
349             return doc.createElementNS(EncryptionConstants.EncryptionSpec11NS, elementName);
350         }
351         return
352             doc.createElementNS(
353                 EncryptionConstants.EncryptionSpec11NS, xenc11Prefix + ":" + elementName
354             );
355     }
356 
357     /**
358      * Returns true if the element is in XML Signature namespace and the local
359      * name equals the supplied one.
360      *
361      * @param element
362      * @param localName
363      * @return true if the element is in XML Signature namespace and the local name equals
364      * the supplied one
365      */
elementIsInSignatureSpace(Element element, String localName)366     public static boolean elementIsInSignatureSpace(Element element, String localName) {
367         if (element == null){
368             return false;
369         }
370 
371         return Constants.SignatureSpecNS.equals(element.getNamespaceURI())
372             && element.getLocalName().equals(localName);
373     }
374 
375     /**
376      * Returns true if the element is in XML Signature 1.1 namespace and the local
377      * name equals the supplied one.
378      *
379      * @param element
380      * @param localName
381      * @return true if the element is in XML Signature namespace and the local name equals
382      * the supplied one
383      */
elementIsInSignature11Space(Element element, String localName)384     public static boolean elementIsInSignature11Space(Element element, String localName) {
385         if (element == null) {
386             return false;
387         }
388 
389         return Constants.SignatureSpec11NS.equals(element.getNamespaceURI())
390             && element.getLocalName().equals(localName);
391     }
392 
393     /**
394      * Returns true if the element is in XML Encryption namespace and the local
395      * name equals the supplied one.
396      *
397      * @param element
398      * @param localName
399      * @return true if the element is in XML Encryption namespace and the local name
400      * equals the supplied one
401      */
elementIsInEncryptionSpace(Element element, String localName)402     public static boolean elementIsInEncryptionSpace(Element element, String localName) {
403         if (element == null){
404             return false;
405         }
406         return EncryptionConstants.EncryptionSpecNS.equals(element.getNamespaceURI())
407             && element.getLocalName().equals(localName);
408     }
409 
410     /**
411      * Returns true if the element is in XML Encryption 1.1 namespace and the local
412      * name equals the supplied one.
413      *
414      * @param element
415      * @param localName
416      * @return true if the element is in XML Encryption 1.1 namespace and the local name
417      * equals the supplied one
418      */
elementIsInEncryption11Space(Element element, String localName)419     public static boolean elementIsInEncryption11Space(Element element, String localName) {
420         if (element == null){
421             return false;
422         }
423         return EncryptionConstants.EncryptionSpec11NS.equals(element.getNamespaceURI())
424             && element.getLocalName().equals(localName);
425     }
426 
427     /**
428      * This method returns the owner document of a particular node.
429      * This method is necessary because it <I>always</I> returns a
430      * {@link Document}. {@link Node#getOwnerDocument} returns {@code null}
431      * if the {@link Node} is a {@link Document}.
432      *
433      * @param node
434      * @return the owner document of the node
435      */
getOwnerDocument(Node node)436     public static Document getOwnerDocument(Node node) {
437         if (node.getNodeType() == Node.DOCUMENT_NODE) {
438             return (Document) node;
439         }
440         try {
441             return node.getOwnerDocument();
442         } catch (NullPointerException npe) {
443             throw new NullPointerException(I18n.translate("endorsed.jdk1.4.0")
444                                            + " Original message was \""
445                                            + npe.getMessage() + "\"");
446         }
447     }
448 
449     /**
450      * This method returns the first non-null owner document of the Nodes in this Set.
451      * This method is necessary because it <I>always</I> returns a
452      * {@link Document}. {@link Node#getOwnerDocument} returns {@code null}
453      * if the {@link Node} is a {@link Document}.
454      *
455      * @param xpathNodeSet
456      * @return the owner document
457      */
getOwnerDocument(Set<Node> xpathNodeSet)458     public static Document getOwnerDocument(Set<Node> xpathNodeSet) {
459         NullPointerException npe = null;
460         for (Node node : xpathNodeSet) {
461             int nodeType = node.getNodeType();
462             if (nodeType == Node.DOCUMENT_NODE) {
463                 return (Document) node;
464             }
465             try {
466                 if (nodeType == Node.ATTRIBUTE_NODE) {
467                     return ((Attr)node).getOwnerElement().getOwnerDocument();
468                 }
469                 return node.getOwnerDocument();
470             } catch (NullPointerException e) {
471                 npe = e;
472             }
473         }
474 
475         throw new NullPointerException(I18n.translate("endorsed.jdk1.4.0")
476                                        + " Original message was \""
477                                        + (npe == null ? "" : npe.getMessage()) + "\"");
478     }
479 
480     /**
481      * Method addReturnToElement
482      *
483      * @param e
484      */
addReturnToElement(Element e)485     public static void addReturnToElement(Element e) {
486         if (!ignoreLineBreaks) {
487             Document doc = e.getOwnerDocument();
488             e.appendChild(doc.createTextNode("\n"));
489         }
490     }
491 
addReturnToElement(Document doc, HelperNodeList nl)492     public static void addReturnToElement(Document doc, HelperNodeList nl) {
493         if (!ignoreLineBreaks) {
494             nl.appendChild(doc.createTextNode("\n"));
495         }
496     }
497 
addReturnBeforeChild(Element e, Node child)498     public static void addReturnBeforeChild(Element e, Node child) {
499         if (!ignoreLineBreaks) {
500             Document doc = e.getOwnerDocument();
501             e.insertBefore(doc.createTextNode("\n"), child);
502         }
503     }
504 
encodeToString(byte[] bytes)505     public static String encodeToString(byte[] bytes) {
506         if (ignoreLineBreaks) {
507             return Base64.getEncoder().encodeToString(bytes);
508         }
509         return Base64.getMimeEncoder().encodeToString(bytes);
510     }
511 
decode(String encodedString)512     public static byte[] decode(String encodedString) {
513         return Base64.getMimeDecoder().decode(encodedString);
514     }
515 
decode(byte[] encodedBytes)516     public static byte[] decode(byte[] encodedBytes) {
517         return Base64.getMimeDecoder().decode(encodedBytes);
518     }
519 
isIgnoreLineBreaks()520     public static boolean isIgnoreLineBreaks() {
521         return ignoreLineBreaks;
522     }
523 
524     /**
525      * Method convertNodelistToSet
526      *
527      * @param xpathNodeSet
528      * @return the set with the nodelist
529      */
convertNodelistToSet(NodeList xpathNodeSet)530     public static Set<Node> convertNodelistToSet(NodeList xpathNodeSet) {
531         if (xpathNodeSet == null) {
532             return new HashSet<>();
533         }
534 
535         int length = xpathNodeSet.getLength();
536         Set<Node> set = new HashSet<>(length);
537 
538         for (int i = 0; i < length; i++) {
539             set.add(xpathNodeSet.item(i));
540         }
541 
542         return set;
543     }
544 
545     /**
546      * This method spreads all namespace attributes in a DOM document to their
547      * children. This is needed because the XML Signature XPath transform
548      * must evaluate the XPath against all nodes in the input, even against
549      * XPath namespace nodes. Through a bug in XalanJ2, the namespace nodes are
550      * not fully visible in the Xalan XPath model, so we have to do this by
551      * hand in DOM spaces so that the nodes become visible in XPath space.
552      *
553      * @param doc
554      * @see <A HREF="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=2650">
555      * Namespace axis resolution is not XPath compliant </A>
556      */
circumventBug2650(Document doc)557     public static void circumventBug2650(Document doc) {
558 
559         Element documentElement = doc.getDocumentElement();
560 
561         // if the document element has no xmlns definition, we add xmlns=""
562         Attr xmlnsAttr =
563             documentElement.getAttributeNodeNS(Constants.NamespaceSpecNS, "xmlns");
564 
565         if (xmlnsAttr == null) {
566             documentElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "");
567         }
568 
569         XMLUtils.circumventBug2650internal(doc);
570     }
571 
572     /**
573      * This is the work horse for {@link #circumventBug2650}.
574      *
575      * @param node
576      * @see <A HREF="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=2650">
577      * Namespace axis resolution is not XPath compliant </A>
578      */
579     @SuppressWarnings("fallthrough")
circumventBug2650internal(Node node)580     private static void circumventBug2650internal(Node node) {
581         Node parent = null;
582         Node sibling = null;
583         final String namespaceNs = Constants.NamespaceSpecNS;
584         do {
585             switch (node.getNodeType()) {
586             case Node.ELEMENT_NODE :
587                 Element element = (Element) node;
588                 if (!element.hasChildNodes()) {
589                     break;
590                 }
591                 if (element.hasAttributes()) {
592                     NamedNodeMap attributes = element.getAttributes();
593                     int attributesLength = attributes.getLength();
594 
595                     for (Node child = element.getFirstChild(); child!=null;
596                         child = child.getNextSibling()) {
597 
598                         if (child.getNodeType() != Node.ELEMENT_NODE) {
599                             continue;
600                         }
601                         Element childElement = (Element) child;
602 
603                         for (int i = 0; i < attributesLength; i++) {
604                             Attr currentAttr = (Attr) attributes.item(i);
605                             if (!namespaceNs.equals(currentAttr.getNamespaceURI())) {
606                                 continue;
607                             }
608                             if (childElement.hasAttributeNS(namespaceNs,
609                                                             currentAttr.getLocalName())) {
610                                 continue;
611                             }
612                             childElement.setAttributeNS(namespaceNs,
613                                                         currentAttr.getName(),
614                                                         currentAttr.getNodeValue());
615                         }
616                     }
617                 }
618             case Node.ENTITY_REFERENCE_NODE :
619             case Node.DOCUMENT_NODE :
620                 parent = node;
621                 sibling = node.getFirstChild();
622                 break;
623             }
624             while (sibling == null && parent != null) {
625                 sibling = parent.getNextSibling();
626                 parent = parent.getParentNode();
627             }
628             if (sibling == null) {
629                 return;
630             }
631 
632             node = sibling;
633             sibling = node.getNextSibling();
634         } while (true);
635     }
636 
637     /**
638      * @param sibling
639      * @param nodeName
640      * @param number
641      * @return nodes with the constraint
642      */
selectDsNode(Node sibling, String nodeName, int number)643     public static Element selectDsNode(Node sibling, String nodeName, int number) {
644         while (sibling != null) {
645             if (Constants.SignatureSpecNS.equals(sibling.getNamespaceURI())
646                 && sibling.getLocalName().equals(nodeName)) {
647                 if (number == 0) {
648                     return (Element)sibling;
649                 }
650                 number--;
651             }
652             sibling = sibling.getNextSibling();
653         }
654         return null;
655     }
656 
657     /**
658      * @param sibling
659      * @param nodeName
660      * @param number
661      * @return nodes with the constraint
662      */
selectDs11Node(Node sibling, String nodeName, int number)663     public static Element selectDs11Node(Node sibling, String nodeName, int number) {
664         while (sibling != null) {
665             if (Constants.SignatureSpec11NS.equals(sibling.getNamespaceURI())
666                 && sibling.getLocalName().equals(nodeName)) {
667                 if (number == 0) {
668                     return (Element)sibling;
669                 }
670                 number--;
671             }
672             sibling = sibling.getNextSibling();
673         }
674         return null;
675     }
676 
677     /**
678      * @param sibling
679      * @param nodeName
680      * @param number
681      * @return nodes with the constrain
682      */
selectXencNode(Node sibling, String nodeName, int number)683     public static Element selectXencNode(Node sibling, String nodeName, int number) {
684         while (sibling != null) {
685             if (EncryptionConstants.EncryptionSpecNS.equals(sibling.getNamespaceURI())
686                 && sibling.getLocalName().equals(nodeName)) {
687                 if (number == 0){
688                     return (Element)sibling;
689                 }
690                 number--;
691             }
692             sibling = sibling.getNextSibling();
693         }
694         return null;
695     }
696 
697     /**
698      * @param sibling
699      * @param uri
700      * @param nodeName
701      * @param number
702      * @return nodes with the constrain
703      */
selectNode(Node sibling, String uri, String nodeName, int number)704     public static Element selectNode(Node sibling, String uri, String nodeName, int number) {
705         while (sibling != null) {
706             if (sibling.getNamespaceURI() != null && sibling.getNamespaceURI().equals(uri)
707                 && sibling.getLocalName().equals(nodeName)) {
708                 if (number == 0) {
709                     return (Element)sibling;
710                 }
711                 number--;
712             }
713             sibling = sibling.getNextSibling();
714         }
715         return null;
716     }
717 
718     /**
719      * @param sibling
720      * @param nodeName
721      * @return nodes with the constrain
722      */
selectDsNodes(Node sibling, String nodeName)723     public static Element[] selectDsNodes(Node sibling, String nodeName) {
724         return selectNodes(sibling, Constants.SignatureSpecNS, nodeName);
725     }
726 
727     /**
728      * @param sibling
729      * @param nodeName
730      * @return nodes with the constrain
731      */
selectDs11Nodes(Node sibling, String nodeName)732     public static Element[] selectDs11Nodes(Node sibling, String nodeName) {
733         return selectNodes(sibling, Constants.SignatureSpec11NS, nodeName);
734     }
735 
736     /**
737      * @param sibling
738      * @param uri
739      * @param nodeName
740      * @return nodes with the constraint
741      */
selectNodes(Node sibling, String uri, String nodeName)742     public static Element[] selectNodes(Node sibling, String uri, String nodeName) {
743         List<Element> list = new ArrayList<>();
744         while (sibling != null) {
745             if (sibling.getNamespaceURI() != null && sibling.getNamespaceURI().equals(uri)
746                 && sibling.getLocalName().equals(nodeName)) {
747                 list.add((Element)sibling);
748             }
749             sibling = sibling.getNextSibling();
750         }
751         return list.toArray(new Element[list.size()]);
752     }
753 
754     /**
755      * @param signatureElement
756      * @param inputSet
757      * @return nodes with the constrain
758      */
excludeNodeFromSet(Node signatureElement, Set<Node> inputSet)759     public static Set<Node> excludeNodeFromSet(Node signatureElement, Set<Node> inputSet) {
760         return inputSet.stream().filter((inputNode) ->
761                 !XMLUtils.isDescendantOrSelf(signatureElement, inputNode)).collect(Collectors.toSet());
762     }
763 
764     /**
765      * Method getStrFromNode
766      *
767      * @param xpathnode
768      * @return the string for the node.
769      */
getStrFromNode(Node xpathnode)770     public static String getStrFromNode(Node xpathnode) {
771         if (xpathnode.getNodeType() == Node.TEXT_NODE) {
772             // we iterate over all siblings of the context node because eventually,
773             // the text is "polluted" with pi's or comments
774             StringBuilder sb = new StringBuilder();
775 
776             for (Node currentSibling = xpathnode.getParentNode().getFirstChild();
777                 currentSibling != null;
778                 currentSibling = currentSibling.getNextSibling()) {
779                 if (currentSibling.getNodeType() == Node.TEXT_NODE) {
780                     sb.append(((Text) currentSibling).getData());
781                 }
782             }
783 
784             return sb.toString();
785         } else if (xpathnode.getNodeType() == Node.ATTRIBUTE_NODE) {
786             return xpathnode.getNodeValue();
787         } else if (xpathnode.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
788             return xpathnode.getNodeValue();
789         }
790 
791         return null;
792     }
793 
794     /**
795      * Returns true if the descendantOrSelf is on the descendant-or-self axis
796      * of the context node.
797      *
798      * @param ctx
799      * @param descendantOrSelf
800      * @return true if the node is descendant
801      */
isDescendantOrSelf(Node ctx, Node descendantOrSelf)802     public static boolean isDescendantOrSelf(Node ctx, Node descendantOrSelf) {
803         if (ctx == descendantOrSelf) {
804             return true;
805         }
806 
807         Node parent = descendantOrSelf;
808 
809         while (true) {
810             if (parent == null) {
811                 return false;
812             }
813 
814             if (parent == ctx) {
815                 return true;
816             }
817 
818             if (parent.getNodeType() == Node.ATTRIBUTE_NODE) {
819                 parent = ((Attr) parent).getOwnerElement();
820             } else {
821                 parent = parent.getParentNode();
822             }
823         }
824     }
825 
ignoreLineBreaks()826     public static boolean ignoreLineBreaks() {
827         return ignoreLineBreaks;
828     }
829 
830     /**
831      * This method is a tree-search to help prevent against wrapping attacks. It checks that no
832      * two Elements have ID Attributes that match the "value" argument, if this is the case then
833      * "false" is returned. Note that a return value of "true" does not necessarily mean that
834      * a matching Element has been found, just that no wrapping attack has been detected.
835      */
protectAgainstWrappingAttack(Node startNode, String value)836     public static boolean protectAgainstWrappingAttack(Node startNode, String value) {
837         String id = value.trim();
838         if (!id.isEmpty() && id.charAt(0) == '#') {
839             id = id.substring(1);
840         }
841 
842         Node startParent = null;
843         Node processedNode = null;
844         Element foundElement = null;
845         if (startNode != null) {
846             startParent = startNode.getParentNode();
847         }
848 
849         while (startNode != null) {
850             if (startNode.getNodeType() == Node.ELEMENT_NODE) {
851                 Element se = (Element) startNode;
852 
853                 NamedNodeMap attributes = se.getAttributes();
854                 if (attributes != null) {
855                     int length = attributes.getLength();
856                     for (int i = 0; i < length; i++) {
857                         Attr attr = (Attr)attributes.item(i);
858                         if (attr.isId() && id.equals(attr.getValue())) {
859                             if (foundElement == null) {
860                                 // Continue searching to find duplicates
861                                 foundElement = attr.getOwnerElement();
862                             } else {
863                                 LOG.debug("Multiple elements with the same 'Id' attribute value!");
864                                 return false;
865                             }
866                         }
867                     }
868                 }
869             }
870 
871             processedNode = startNode;
872             startNode = startNode.getFirstChild();
873 
874             // no child, this node is done.
875             if (startNode == null) {
876                 // close node processing, get sibling
877                 startNode = processedNode.getNextSibling();
878             }
879 
880             // no more siblings, get parent, all children
881             // of parent are processed.
882             while (startNode == null) {
883                 processedNode = processedNode.getParentNode();
884                 if (processedNode == startParent) {
885                     return true;
886                 }
887                 // close parent node processing (processed node now)
888                 startNode = processedNode.getNextSibling();
889             }
890         }
891         return true;
892     }
893 
894     /**
895      * This method is a tree-search to help prevent against wrapping attacks. It checks that no other
896      * Element than the given "knownElement" argument has an ID attribute that matches the "value"
897      * argument, which is the ID value of "knownElement". If this is the case then "false" is returned.
898      */
protectAgainstWrappingAttack( Node startNode, Element knownElement, String value )899     public static boolean protectAgainstWrappingAttack(
900         Node startNode, Element knownElement, String value
901     ) {
902         String id = value.trim();
903         if (!id.isEmpty() && id.charAt(0) == '#') {
904             id = id.substring(1);
905         }
906 
907         Node startParent = null;
908         Node processedNode = null;
909         if (startNode != null) {
910             startParent = startNode.getParentNode();
911         }
912 
913         while (startNode != null) {
914             if (startNode.getNodeType() == Node.ELEMENT_NODE) {
915                 Element se = (Element) startNode;
916 
917                 NamedNodeMap attributes = se.getAttributes();
918                 if (attributes != null) {
919                     int length = attributes.getLength();
920                     for (int i = 0; i < length; i++) {
921                         Attr attr = (Attr)attributes.item(i);
922                         if (attr.isId() && id.equals(attr.getValue()) && se != knownElement) {
923                             LOG.debug("Multiple elements with the same 'Id' attribute value!");
924                             return false;
925                         }
926                     }
927                 }
928             }
929 
930             processedNode = startNode;
931             startNode = startNode.getFirstChild();
932 
933             // no child, this node is done.
934             if (startNode == null) {
935                 // close node processing, get sibling
936                 startNode = processedNode.getNextSibling();
937             }
938 
939             // no more siblings, get parent, all children
940             // of parent are processed.
941             while (startNode == null) {
942                 processedNode = processedNode.getParentNode();
943                 if (processedNode == startParent) {
944                     return true;
945                 }
946                 // close parent node processing (processed node now)
947                 startNode = processedNode.getNextSibling();
948             }
949         }
950         return true;
951     }
952 
read(InputStream inputStream, boolean disallowDocTypeDeclarations)953     public static Document read(InputStream inputStream, boolean disallowDocTypeDeclarations) throws XMLParserException {
954         // Delegate to XMLParser implementation
955         return xmlParserImpl.parse(inputStream, disallowDocTypeDeclarations);
956     }
957 
958     /**
959      * Returns a byte-array representation of a {@code {@link BigInteger}}.
960      * No sign-bit is output.
961      *
962      * <b>N.B.:</B> {@code {@link BigInteger}}'s toByteArray
963      * returns eventually longer arrays because of the leading sign-bit.
964      *
965      * @param big {@code BigInteger} to be converted
966      * @param bitlen {@code int} the desired length in bits of the representation
967      * @return a byte array with {@code bitlen} bits of {@code big}
968      */
getBytes(BigInteger big, int bitlen)969     public static byte[] getBytes(BigInteger big, int bitlen) {
970 
971         //round bitlen
972         bitlen = ((bitlen + 7) >> 3) << 3;
973 
974         if (bitlen < big.bitLength()) {
975             throw new IllegalArgumentException(I18n.translate("utils.Base64.IllegalBitlength"));
976         }
977 
978         byte[] bigBytes = big.toByteArray();
979 
980         if (big.bitLength() % 8 != 0
981             && big.bitLength() / 8 + 1 == bitlen / 8) {
982             return bigBytes;
983         }
984 
985         // some copying needed
986         int startSrc = 0;    // no need to skip anything
987         int bigLen = bigBytes.length;    //valid length of the string
988 
989         if (big.bitLength() % 8 == 0) {    // correct values
990             startSrc = 1;    // skip sign bit
991 
992             bigLen--;    // valid length of the string
993         }
994 
995         int startDst = bitlen / 8 - bigLen;    //pad with leading nulls
996         byte[] resizedBytes = new byte[bitlen / 8];
997 
998         System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, bigLen);
999 
1000         return resizedBytes;
1001     }
1002 
1003 
1004 
1005 }
1006