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