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