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.signature;
24 
25 import java.io.ByteArrayInputStream;
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.nio.charset.StandardCharsets;
31 import java.util.ArrayList;
32 import java.util.LinkedHashSet;
33 import java.util.List;
34 import java.util.Set;
35 
36 import javax.xml.parsers.DocumentBuilder;
37 import javax.xml.parsers.ParserConfigurationException;
38 
39 import com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException;
40 import com.sun.org.apache.xml.internal.security.c14n.implementations.Canonicalizer11_OmitComments;
41 import com.sun.org.apache.xml.internal.security.c14n.implementations.Canonicalizer20010315OmitComments;
42 import com.sun.org.apache.xml.internal.security.c14n.implementations.CanonicalizerBase;
43 import com.sun.org.apache.xml.internal.security.exceptions.XMLSecurityRuntimeException;
44 import com.sun.org.apache.xml.internal.security.utils.JavaUtils;
45 import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.Node;
48 import org.xml.sax.SAXException;
49 
50 /**
51  * Class XMLSignatureInput
52  *
53  * $todo$ check whether an XMLSignatureInput can be _both_, octet stream _and_ node set?
54  */
55 public class XMLSignatureInput {
56     /*
57      * The XMLSignature Input can be either:
58      *   A byteArray like with/or without InputStream.
59      *   Or a nodeSet like defined either:
60      *       * as a collection of nodes
61      *       * or as subnode excluding or not comments and excluding or
62      *         not other nodes.
63      */
64 
65     /**
66      * Some InputStreams do not support the {@link java.io.InputStream#reset}
67      * method, so we read it in completely and work on our Proxy.
68      */
69     private InputStream inputOctetStreamProxy;
70     /**
71      * The original NodeSet for this XMLSignatureInput
72      */
73     private Set<Node> inputNodeSet;
74     /**
75      * The original Element
76      */
77     private Node subNode;
78     /**
79      * Exclude Node *for enveloped transformations*
80      */
81     private Node excludeNode;
82     /**
83      *
84      */
85     private boolean excludeComments = false;
86 
87     private boolean isNodeSet = false;
88     /**
89      * A cached bytes
90      */
91     private byte[] bytes;
92     private boolean secureValidation;
93 
94     /**
95      * Some Transforms may require explicit MIME type, charset (IANA registered
96      * "character set"), or other such information concerning the data they are
97      * receiving from an earlier Transform or the source data, although no
98      * Transform algorithm specified in this document needs such explicit
99      * information. Such data characteristics are provided as parameters to the
100      * Transform algorithm and should be described in the specification for the
101      * algorithm.
102      */
103     private String mimeType;
104 
105     /**
106      * Field sourceURI
107      */
108     private String sourceURI;
109 
110     /**
111      * Node Filter list.
112      */
113     private List<NodeFilter> nodeFilters = new ArrayList<>();
114 
115     private boolean needsToBeExpanded = false;
116     private OutputStream outputStream;
117 
118     /**
119      * Pre-calculated digest value of the object in base64.
120      */
121     private String preCalculatedDigest;
122 
123     /**
124      * Construct a XMLSignatureInput from an octet array.
125      * <p>
126      * This is a comfort method, which internally converts the byte[] array into
127      * an InputStream
128      * <p>NOTE: no defensive copy</p>
129      * @param inputOctets an octet array which including XML document or node
130      */
XMLSignatureInput(byte[] inputOctets)131     public XMLSignatureInput(byte[] inputOctets) {
132         // NO defensive copy
133         this.bytes = inputOctets;
134     }
135 
136     /**
137      * Constructs a {@code XMLSignatureInput} from an octet stream. The
138      * stream is directly read.
139      *
140      * @param inputOctetStream
141      */
XMLSignatureInput(InputStream inputOctetStream)142     public XMLSignatureInput(InputStream inputOctetStream)  {
143         this.inputOctetStreamProxy = inputOctetStream;
144     }
145 
146     /**
147      * Construct a XMLSignatureInput from a subtree rooted by rootNode. This
148      * method included the node and <I>all</I> his descendants in the output.
149      *
150      * @param rootNode
151      */
XMLSignatureInput(Node rootNode)152     public XMLSignatureInput(Node rootNode) {
153         this.subNode = rootNode;
154     }
155 
156     /**
157      * Constructor XMLSignatureInput
158      *
159      * @param inputNodeSet
160      */
XMLSignatureInput(Set<Node> inputNodeSet)161     public XMLSignatureInput(Set<Node> inputNodeSet) {
162         this.inputNodeSet = inputNodeSet;
163     }
164 
165     /**
166      * Construct a {@code XMLSignatureInput} from a known digest value in Base64.
167      * This makes it possible to compare the element digest with the provided digest value.
168      * @param preCalculatedDigest digest value in base64.
169      */
XMLSignatureInput(String preCalculatedDigest)170     public XMLSignatureInput(String preCalculatedDigest) {
171         this.preCalculatedDigest = preCalculatedDigest;
172     }
173 
174     /**
175      * Check if the structure needs to be expanded.
176      * @return true if so.
177      */
isNeedsToBeExpanded()178     public boolean isNeedsToBeExpanded() {
179         return needsToBeExpanded;
180     }
181 
182     /**
183      * Set if the structure needs to be expanded.
184      * @param needsToBeExpanded true if so.
185      */
setNeedsToBeExpanded(boolean needsToBeExpanded)186     public void setNeedsToBeExpanded(boolean needsToBeExpanded) {
187         this.needsToBeExpanded = needsToBeExpanded;
188     }
189 
190     /**
191      * Returns the node set from input which was specified as the parameter of
192      * {@link XMLSignatureInput} constructor
193      *
194      * @return the node set
195      * @throws SAXException
196      * @throws IOException
197      * @throws ParserConfigurationException
198      * @throws CanonicalizationException
199      */
getNodeSet()200     public Set<Node> getNodeSet() throws CanonicalizationException, ParserConfigurationException,
201         IOException, SAXException {
202         return getNodeSet(false);
203     }
204 
205     /**
206      * Get the Input NodeSet.
207      * @return the Input NodeSet.
208      */
getInputNodeSet()209     public Set<Node> getInputNodeSet() {
210         return inputNodeSet;
211     }
212 
213     /**
214      * Returns the node set from input which was specified as the parameter of
215      * {@link XMLSignatureInput} constructor
216      * @param circumvent
217      *
218      * @return the node set
219      * @throws SAXException
220      * @throws IOException
221      * @throws ParserConfigurationException
222      * @throws CanonicalizationException
223      */
getNodeSet(boolean circumvent)224     public Set<Node> getNodeSet(boolean circumvent) throws ParserConfigurationException,
225         IOException, SAXException, CanonicalizationException {
226         if (inputNodeSet != null) {
227             return inputNodeSet;
228         }
229         if (inputOctetStreamProxy == null && subNode != null) {
230             if (circumvent) {
231                 XMLUtils.circumventBug2650(XMLUtils.getOwnerDocument(subNode));
232             }
233             inputNodeSet = new LinkedHashSet<Node>();
234             XMLUtils.getSet(subNode, inputNodeSet, excludeNode, excludeComments);
235             return inputNodeSet;
236         } else if (isOctetStream()) {
237             convertToNodes();
238             Set<Node> result = new LinkedHashSet<Node>();
239             XMLUtils.getSet(subNode, result, null, false);
240             return result;
241         }
242 
243         throw new RuntimeException("getNodeSet() called but no input data present");
244     }
245 
246     /**
247      * Returns the Octet stream(byte Stream) from input which was specified as
248      * the parameter of {@link XMLSignatureInput} constructor
249      *
250      * @return the Octet stream(byte Stream) from input which was specified as
251      * the parameter of {@link XMLSignatureInput} constructor
252      * @throws IOException
253      */
getOctetStream()254     public InputStream getOctetStream() throws IOException  {
255         if (inputOctetStreamProxy != null) {
256             return inputOctetStreamProxy;
257         }
258 
259         if (bytes != null) {
260             inputOctetStreamProxy = new ByteArrayInputStream(bytes);
261             return inputOctetStreamProxy;
262         }
263 
264         return null;
265     }
266 
267     /**
268      * @return real octet stream
269      */
getOctetStreamReal()270     public InputStream getOctetStreamReal() {
271         return inputOctetStreamProxy;
272     }
273 
274     /**
275      * Returns the byte array from input which was specified as the parameter of
276      * {@link XMLSignatureInput} constructor
277      *
278      * @return the byte[] from input which was specified as the parameter of
279      * {@link XMLSignatureInput} constructor
280      *
281      * @throws CanonicalizationException
282      * @throws IOException
283      */
getBytes()284     public byte[] getBytes() throws IOException, CanonicalizationException {
285         byte[] inputBytes = getBytesFromInputStream();
286         if (inputBytes != null) {
287             return inputBytes;
288         }
289         Canonicalizer20010315OmitComments c14nizer = new Canonicalizer20010315OmitComments();
290         bytes = c14nizer.engineCanonicalize(this);
291         return bytes;
292     }
293 
294     /**
295      * Determines if the object has been set up with a Node set
296      *
297      * @return true if the object has been set up with a Node set
298      */
isNodeSet()299     public boolean isNodeSet() {
300         return inputOctetStreamProxy == null && inputNodeSet != null || isNodeSet;
301     }
302 
303     /**
304      * Determines if the object has been set up with an Element
305      *
306      * @return true if the object has been set up with an Element
307      */
isElement()308     public boolean isElement() {
309         return inputOctetStreamProxy == null && subNode != null
310             && inputNodeSet == null && !isNodeSet;
311     }
312 
313     /**
314      * Determines if the object has been set up with an octet stream
315      *
316      * @return true if the object has been set up with an octet stream
317      */
isOctetStream()318     public boolean isOctetStream() {
319         return (inputOctetStreamProxy != null || bytes != null)
320           && inputNodeSet == null && subNode == null;
321     }
322 
323     /**
324      * Determines if {@link #setOutputStream} has been called with a
325      * non-null OutputStream.
326      *
327      * @return true if {@link #setOutputStream} has been called with a
328      * non-null OutputStream
329      */
isOutputStreamSet()330     public boolean isOutputStreamSet() {
331         return outputStream != null;
332     }
333 
334     /**
335      * Determines if the object has been set up with a ByteArray
336      *
337      * @return true is the object has been set up with an octet stream
338      */
isByteArray()339     public boolean isByteArray() {
340         return bytes != null && this.inputNodeSet == null && subNode == null;
341     }
342 
343     /**
344      * Determines if the object has been set up with a pre-calculated digest.
345      * @return
346      */
isPreCalculatedDigest()347     public boolean isPreCalculatedDigest() {
348         return preCalculatedDigest != null;
349     }
350 
351     /**
352      * Is the object correctly set up?
353      *
354      * @return true if the object has been set up correctly
355      */
isInitialized()356     public boolean isInitialized() {
357         return isOctetStream() || isNodeSet();
358     }
359 
360     /**
361      * Returns mimeType
362      *
363      * @return mimeType
364      */
getMIMEType()365     public String getMIMEType() {
366         return mimeType;
367     }
368 
369     /**
370      * Sets mimeType
371      *
372      * @param mimeType
373      */
setMIMEType(String mimeType)374     public void setMIMEType(String mimeType) {
375         this.mimeType = mimeType;
376     }
377 
378     /**
379      * Return SourceURI
380      *
381      * @return SourceURI
382      */
getSourceURI()383     public String getSourceURI() {
384         return sourceURI;
385     }
386 
387     /**
388      * Sets SourceURI
389      *
390      * @param sourceURI
391      */
setSourceURI(String sourceURI)392     public void setSourceURI(String sourceURI) {
393         this.sourceURI = sourceURI;
394     }
395 
396     /**
397      * Method toString
398      * {@inheritDoc}
399      */
toString()400     public String toString() {
401         if (isNodeSet()) {
402             return "XMLSignatureInput/NodeSet/" + inputNodeSet.size()
403                    + " nodes/" + getSourceURI();
404         }
405         if (isElement()) {
406             return "XMLSignatureInput/Element/" + subNode
407                 + " exclude "+ excludeNode + " comments:"
408                 + excludeComments +"/" + getSourceURI();
409         }
410         try {
411             return "XMLSignatureInput/OctetStream/" + getBytes().length
412                    + " octets/" + getSourceURI();
413         } catch (IOException iex) {
414             return "XMLSignatureInput/OctetStream//" + getSourceURI();
415         } catch (CanonicalizationException cex) {
416             return "XMLSignatureInput/OctetStream//" + getSourceURI();
417         }
418     }
419 
420     /**
421      * Method getHTMLRepresentation
422      *
423      * @throws XMLSignatureException
424      * @return The HTML representation for this XMLSignature
425      */
getHTMLRepresentation()426     public String getHTMLRepresentation() throws XMLSignatureException {
427         XMLSignatureInputDebugger db = new XMLSignatureInputDebugger(this);
428         return db.getHTMLRepresentation();
429     }
430 
431     /**
432      * Method getHTMLRepresentation
433      *
434      * @param inclusiveNamespaces
435      * @throws XMLSignatureException
436      * @return The HTML representation for this XMLSignature
437      */
getHTMLRepresentation(Set<String> inclusiveNamespaces)438     public String getHTMLRepresentation(Set<String> inclusiveNamespaces)
439        throws XMLSignatureException {
440         XMLSignatureInputDebugger db =
441             new XMLSignatureInputDebugger(this, inclusiveNamespaces);
442         return db.getHTMLRepresentation();
443     }
444 
445     /**
446      * Gets the exclude node of this XMLSignatureInput
447      * @return Returns the excludeNode.
448      */
getExcludeNode()449     public Node getExcludeNode() {
450         return excludeNode;
451     }
452 
453     /**
454      * Sets the exclude node of this XMLSignatureInput
455      * @param excludeNode The excludeNode to set.
456      */
setExcludeNode(Node excludeNode)457     public void setExcludeNode(Node excludeNode) {
458         this.excludeNode = excludeNode;
459     }
460 
461     /**
462      * Gets the node of this XMLSignatureInput
463      * @return The excludeNode set.
464      */
getSubNode()465     public Node getSubNode() {
466         return subNode;
467     }
468 
469     /**
470      * @return Returns the excludeComments.
471      */
isExcludeComments()472     public boolean isExcludeComments() {
473         return excludeComments;
474     }
475 
476     /**
477      * @param excludeComments The excludeComments to set.
478      */
setExcludeComments(boolean excludeComments)479     public void setExcludeComments(boolean excludeComments) {
480         this.excludeComments = excludeComments;
481     }
482 
483     /**
484      * @param diOs
485      * @throws IOException
486      * @throws CanonicalizationException
487      */
updateOutputStream(OutputStream diOs)488     public void updateOutputStream(OutputStream diOs)
489         throws CanonicalizationException, IOException {
490         updateOutputStream(diOs, false);
491     }
492 
updateOutputStream(OutputStream diOs, boolean c14n11)493     public void updateOutputStream(OutputStream diOs, boolean c14n11)
494         throws CanonicalizationException, IOException {
495         if (diOs == outputStream) {
496             return;
497         }
498         if (bytes != null) {
499             diOs.write(bytes);
500         } else if (inputOctetStreamProxy == null) {
501             CanonicalizerBase c14nizer = null;
502             if (c14n11) {
503                 c14nizer = new Canonicalizer11_OmitComments();
504             } else {
505                 c14nizer = new Canonicalizer20010315OmitComments();
506             }
507             c14nizer.setWriter(diOs);
508             c14nizer.engineCanonicalize(this);
509         } else {
510             byte[] buffer = new byte[4 * 1024];
511             int bytesread = 0;
512             try {
513                 while ((bytesread = inputOctetStreamProxy.read(buffer)) != -1) {
514                     diOs.write(buffer, 0, bytesread);
515                 }
516             } catch (IOException ex) {
517                 inputOctetStreamProxy.close();
518                 throw ex;
519             }
520         }
521     }
522 
523     /**
524      * @param os
525      */
setOutputStream(OutputStream os)526     public void setOutputStream(OutputStream os) {
527         outputStream = os;
528     }
529 
getBytesFromInputStream()530     private byte[] getBytesFromInputStream() throws IOException {
531         if (bytes != null) {
532             return bytes;
533         }
534         if (inputOctetStreamProxy == null) {
535             return null;
536         }
537         try {
538             bytes = JavaUtils.getBytesFromStream(inputOctetStreamProxy);
539         } finally {
540             inputOctetStreamProxy.close();
541         }
542         return bytes;
543     }
544 
545     /**
546      * @param filter
547      */
addNodeFilter(NodeFilter filter)548     public void addNodeFilter(NodeFilter filter) {
549         if (isOctetStream()) {
550             try {
551                 convertToNodes();
552             } catch (Exception e) {
553                 throw new XMLSecurityRuntimeException(
554                     "signature.XMLSignatureInput.nodesetReference", e
555                 );
556             }
557         }
558         nodeFilters.add(filter);
559     }
560 
561     /**
562      * @return the node filters
563      */
getNodeFilters()564     public List<NodeFilter> getNodeFilters() {
565         return nodeFilters;
566     }
567 
568     /**
569      * @param b
570      */
setNodeSet(boolean b)571     public void setNodeSet(boolean b) {
572         isNodeSet = b;
573     }
574 
convertToNodes()575     void convertToNodes() throws CanonicalizationException,
576         ParserConfigurationException, IOException, SAXException {
577         DocumentBuilder db = XMLUtils.createDocumentBuilder(false, secureValidation);
578         // select all nodes, also the comments.
579         try {
580             db.setErrorHandler(new com.sun.org.apache.xml.internal.security.utils.IgnoreAllErrorHandler());
581 
582             Document doc = db.parse(this.getOctetStream());
583             this.subNode = doc;
584         } catch (SAXException ex) {
585             byte[] result = null;
586             // if a not-wellformed nodeset exists, put a container around it...
587             try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
588 
589                 baos.write("<container>".getBytes(StandardCharsets.UTF_8));
590                 baos.write(this.getBytes());
591                 baos.write("</container>".getBytes(StandardCharsets.UTF_8));
592 
593                 result = baos.toByteArray();
594             }
595             try (InputStream is = new ByteArrayInputStream(result)) {
596                 Document document = db.parse(is);
597                 this.subNode = document.getDocumentElement().getFirstChild().getFirstChild();
598             }
599         } finally {
600             if (this.inputOctetStreamProxy != null) {
601                 this.inputOctetStreamProxy.close();
602             }
603             this.inputOctetStreamProxy = null;
604             this.bytes = null;
605         }
606     }
607 
isSecureValidation()608     public boolean isSecureValidation() {
609         return secureValidation;
610     }
611 
setSecureValidation(boolean secureValidation)612     public void setSecureValidation(boolean secureValidation) {
613         this.secureValidation = secureValidation;
614     }
615 
getPreCalculatedDigest()616     public String getPreCalculatedDigest() {
617         return preCalculatedDigest;
618     }
619 }
620