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 /* 24 * Portions copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. 25 */ 26 /* 27 * =========================================================================== 28 * 29 * (C) Copyright IBM Corp. 2003 All Rights Reserved. 30 * 31 * =========================================================================== 32 */ 33 /* 34 * $Id: DOMReference.java 1854026 2019-02-21 09:30:01Z coheigea $ 35 */ 36 package org.jcp.xml.dsig.internal.dom; 37 38 import javax.xml.crypto.*; 39 import javax.xml.crypto.dsig.*; 40 import javax.xml.crypto.dom.DOMCryptoContext; 41 import javax.xml.crypto.dom.DOMURIReference; 42 43 import java.io.*; 44 import java.net.URI; 45 import java.net.URISyntaxException; 46 import java.security.*; 47 import java.util.*; 48 49 import org.w3c.dom.Attr; 50 import org.w3c.dom.Document; 51 import org.w3c.dom.Element; 52 import org.w3c.dom.Node; 53 54 import com.sun.org.apache.xml.internal.security.utils.XMLUtils; 55 56 import org.jcp.xml.dsig.internal.DigesterOutputStream; 57 import com.sun.org.apache.xml.internal.security.signature.XMLSignatureInput; 58 import com.sun.org.apache.xml.internal.security.utils.UnsyncBufferedOutputStream; 59 60 /** 61 * DOM-based implementation of Reference. 62 * 63 */ 64 public final class DOMReference extends DOMStructure 65 implements Reference, DOMURIReference { 66 67 /** 68 * The maximum number of transforms per reference, if secure validation is enabled. 69 */ 70 public static final int MAXIMUM_TRANSFORM_COUNT = 5; 71 72 /** 73 * Look up useC14N11 system property. If true, an explicit C14N11 transform 74 * will be added if necessary when generating the signature. See section 75 * 3.1.1 of http://www.w3.org/2007/xmlsec/Drafts/xmldsig-core/ for more info. 76 * 77 * If true, overrides the same property if set in the XMLSignContext. 78 */ 79 private static boolean useC14N11 = 80 AccessController.doPrivileged((PrivilegedAction<Boolean>) 81 () -> Boolean.getBoolean("com.sun.org.apache.xml.internal.security.useC14N11")); 82 83 private static final com.sun.org.slf4j.internal.Logger LOG = 84 com.sun.org.slf4j.internal.LoggerFactory.getLogger(DOMReference.class); 85 86 private final DigestMethod digestMethod; 87 private final String id; 88 private final List<Transform> transforms; 89 private List<Transform> allTransforms; 90 private final Data appliedTransformData; 91 private Attr here; 92 private final String uri; 93 private final String type; 94 private byte[] digestValue; 95 private byte[] calcDigestValue; 96 private Element refElem; 97 private boolean digested = false; 98 private boolean validated = false; 99 private boolean validationStatus; 100 private Data derefData; 101 private InputStream dis; 102 private MessageDigest md; 103 private Provider provider; 104 105 /** 106 * Creates a {@code Reference} from the specified parameters. 107 * 108 * @param uri the URI (may be null) 109 * @param type the type (may be null) 110 * @param dm the digest method 111 * @param transforms a list of {@link Transform}s. The list 112 * is defensively copied to protect against subsequent modification. 113 * May be {@code null} or empty. 114 * @param id the reference ID (may be {@code null}) 115 * @throws NullPointerException if {@code dm} is {@code null} 116 * @throws ClassCastException if any of the {@code transforms} are 117 * not of type {@code Transform} 118 */ DOMReference(String uri, String type, DigestMethod dm, List<? extends Transform> transforms, String id, Provider provider)119 public DOMReference(String uri, String type, DigestMethod dm, 120 List<? extends Transform> transforms, String id, 121 Provider provider) 122 { 123 this(uri, type, dm, null, null, transforms, id, null, provider); 124 } 125 DOMReference(String uri, String type, DigestMethod dm, List<? extends Transform> appliedTransforms, Data result, List<? extends Transform> transforms, String id, Provider provider)126 public DOMReference(String uri, String type, DigestMethod dm, 127 List<? extends Transform> appliedTransforms, 128 Data result, List<? extends Transform> transforms, 129 String id, Provider provider) 130 { 131 this(uri, type, dm, appliedTransforms, 132 result, transforms, id, null, provider); 133 } 134 DOMReference(String uri, String type, DigestMethod dm, List<? extends Transform> appliedTransforms, Data result, List<? extends Transform> transforms, String id, byte[] digestValue, Provider provider)135 public DOMReference(String uri, String type, DigestMethod dm, 136 List<? extends Transform> appliedTransforms, 137 Data result, List<? extends Transform> transforms, 138 String id, byte[] digestValue, Provider provider) 139 { 140 if (dm == null) { 141 throw new NullPointerException("DigestMethod must be non-null"); 142 } 143 if (appliedTransforms == null) { 144 this.allTransforms = new ArrayList<>(); 145 } else { 146 this.allTransforms = new ArrayList<>(appliedTransforms); 147 for (int i = 0, size = this.allTransforms.size(); i < size; i++) { 148 if (!(this.allTransforms.get(i) instanceof Transform)) { 149 throw new ClassCastException 150 ("appliedTransforms["+i+"] is not a valid type"); 151 } 152 } 153 } 154 if (transforms == null) { 155 this.transforms = Collections.emptyList(); 156 } else { 157 this.transforms = new ArrayList<>(transforms); 158 for (int i = 0, size = this.transforms.size(); i < size; i++) { 159 if (!(this.transforms.get(i) instanceof Transform)) { 160 throw new ClassCastException 161 ("transforms["+i+"] is not a valid type"); 162 } 163 } 164 this.allTransforms.addAll(this.transforms); 165 } 166 this.digestMethod = dm; 167 this.uri = uri; 168 if (uri != null && !uri.equals("")) { 169 try { 170 new URI(uri); 171 } catch (URISyntaxException e) { 172 throw new IllegalArgumentException(e.getMessage()); 173 } 174 } 175 this.type = type; 176 this.id = id; 177 if (digestValue != null) { 178 this.digestValue = digestValue.clone(); 179 this.digested = true; 180 } 181 this.appliedTransformData = result; 182 this.provider = provider; 183 } 184 185 /** 186 * Creates a {@code DOMReference} from an element. 187 * 188 * @param refElem a Reference element 189 */ DOMReference(Element refElem, XMLCryptoContext context, Provider provider)190 public DOMReference(Element refElem, XMLCryptoContext context, 191 Provider provider) 192 throws MarshalException 193 { 194 boolean secVal = Utils.secureValidation(context); 195 196 // unmarshal Transforms, if specified 197 Element nextSibling = DOMUtils.getFirstChildElement(refElem); 198 List<Transform> newTransforms = new ArrayList<>(MAXIMUM_TRANSFORM_COUNT); 199 if (nextSibling.getLocalName().equals("Transforms") 200 && XMLSignature.XMLNS.equals(nextSibling.getNamespaceURI())) { 201 Element transformElem = DOMUtils.getFirstChildElement(nextSibling, 202 "Transform", 203 XMLSignature.XMLNS); 204 newTransforms.add(new DOMTransform(transformElem, context, provider)); 205 transformElem = DOMUtils.getNextSiblingElement(transformElem); 206 while (transformElem != null) { 207 String localName = transformElem.getLocalName(); 208 String namespace = transformElem.getNamespaceURI(); 209 if (!"Transform".equals(localName) || !XMLSignature.XMLNS.equals(namespace)) { 210 throw new MarshalException( 211 "Invalid element name: " + localName + 212 ", expected Transform"); 213 } 214 newTransforms.add 215 (new DOMTransform(transformElem, context, provider)); 216 if (secVal && Policy.restrictNumTransforms(newTransforms.size())) { 217 String error = "A maximum of " + Policy.maxTransforms() 218 + " transforms per Reference are allowed when" 219 + " secure validation is enabled"; 220 throw new MarshalException(error); 221 } 222 transformElem = DOMUtils.getNextSiblingElement(transformElem); 223 } 224 nextSibling = DOMUtils.getNextSiblingElement(nextSibling); 225 } 226 if (!nextSibling.getLocalName().equals("DigestMethod") 227 && XMLSignature.XMLNS.equals(nextSibling.getNamespaceURI())) { 228 throw new MarshalException("Invalid element name: " + 229 nextSibling.getLocalName() + 230 ", expected DigestMethod"); 231 } 232 233 // unmarshal DigestMethod 234 Element dmElem = nextSibling; 235 this.digestMethod = DOMDigestMethod.unmarshal(dmElem); 236 String digestMethodAlgorithm = this.digestMethod.getAlgorithm(); 237 if (secVal && Policy.restrictAlg(digestMethodAlgorithm)) { 238 throw new MarshalException( 239 "It is forbidden to use algorithm " + digestMethodAlgorithm + 240 " when secure validation is enabled" 241 ); 242 } 243 244 // unmarshal DigestValue 245 Element dvElem = DOMUtils.getNextSiblingElement(dmElem, "DigestValue", XMLSignature.XMLNS); 246 String content = XMLUtils.getFullTextChildrenFromNode(dvElem); 247 this.digestValue = XMLUtils.decode(content); 248 249 // check for extra elements 250 if (DOMUtils.getNextSiblingElement(dvElem) != null) { 251 throw new MarshalException( 252 "Unexpected element after DigestValue element"); 253 } 254 255 // unmarshal attributes 256 this.uri = DOMUtils.getAttributeValue(refElem, "URI"); 257 258 Attr attr = refElem.getAttributeNodeNS(null, "Id"); 259 if (attr != null) { 260 this.id = attr.getValue(); 261 refElem.setIdAttributeNode(attr, true); 262 } else { 263 this.id = null; 264 } 265 266 this.type = DOMUtils.getAttributeValue(refElem, "Type"); 267 this.here = refElem.getAttributeNodeNS(null, "URI"); 268 this.refElem = refElem; 269 this.transforms = newTransforms; 270 this.allTransforms = transforms; 271 this.appliedTransformData = null; 272 this.provider = provider; 273 } 274 getDigestMethod()275 public DigestMethod getDigestMethod() { 276 return digestMethod; 277 } 278 getId()279 public String getId() { 280 return id; 281 } 282 getURI()283 public String getURI() { 284 return uri; 285 } 286 getType()287 public String getType() { 288 return type; 289 } 290 getTransforms()291 public List<Transform> getTransforms() { 292 return Collections.unmodifiableList(allTransforms); 293 } 294 getDigestValue()295 public byte[] getDigestValue() { 296 return digestValue == null ? null : digestValue.clone(); 297 } 298 getCalculatedDigestValue()299 public byte[] getCalculatedDigestValue() { 300 return calcDigestValue == null ? null 301 : calcDigestValue.clone(); 302 } 303 304 @Override marshal(Node parent, String dsPrefix, DOMCryptoContext context)305 public void marshal(Node parent, String dsPrefix, DOMCryptoContext context) 306 throws MarshalException 307 { 308 LOG.debug("Marshalling Reference"); 309 Document ownerDoc = DOMUtils.getOwnerDocument(parent); 310 311 refElem = DOMUtils.createElement(ownerDoc, "Reference", 312 XMLSignature.XMLNS, dsPrefix); 313 314 // set attributes 315 DOMUtils.setAttributeID(refElem, "Id", id); 316 DOMUtils.setAttribute(refElem, "URI", uri); 317 DOMUtils.setAttribute(refElem, "Type", type); 318 319 // create and append Transforms element 320 if (!allTransforms.isEmpty()) { 321 Element transformsElem = DOMUtils.createElement(ownerDoc, 322 "Transforms", 323 XMLSignature.XMLNS, 324 dsPrefix); 325 refElem.appendChild(transformsElem); 326 for (Transform transform : allTransforms) { 327 ((DOMStructure)transform).marshal(transformsElem, 328 dsPrefix, context); 329 } 330 } 331 332 // create and append DigestMethod element 333 ((DOMDigestMethod)digestMethod).marshal(refElem, dsPrefix, context); 334 335 // create and append DigestValue element 336 LOG.debug("Adding digestValueElem"); 337 Element digestValueElem = DOMUtils.createElement(ownerDoc, 338 "DigestValue", 339 XMLSignature.XMLNS, 340 dsPrefix); 341 if (digestValue != null) { 342 digestValueElem.appendChild 343 (ownerDoc.createTextNode(XMLUtils.encodeToString(digestValue))); 344 } 345 refElem.appendChild(digestValueElem); 346 347 parent.appendChild(refElem); 348 here = refElem.getAttributeNodeNS(null, "URI"); 349 } 350 digest(XMLSignContext signContext)351 public void digest(XMLSignContext signContext) 352 throws XMLSignatureException 353 { 354 Data data = null; 355 if (appliedTransformData == null) { 356 data = dereference(signContext); 357 } else { 358 data = appliedTransformData; 359 } 360 digestValue = transform(data, signContext); 361 362 // insert digestValue into DigestValue element 363 String encodedDV = XMLUtils.encodeToString(digestValue); 364 LOG.debug("Reference object uri = {}", uri); 365 Element digestElem = DOMUtils.getLastChildElement(refElem); 366 if (digestElem == null) { 367 throw new XMLSignatureException("DigestValue element expected"); 368 } 369 DOMUtils.removeAllChildren(digestElem); 370 digestElem.appendChild 371 (refElem.getOwnerDocument().createTextNode(encodedDV)); 372 373 digested = true; 374 LOG.debug("Reference digesting completed"); 375 } 376 validate(XMLValidateContext validateContext)377 public boolean validate(XMLValidateContext validateContext) 378 throws XMLSignatureException 379 { 380 if (validateContext == null) { 381 throw new NullPointerException("validateContext cannot be null"); 382 } 383 if (validated) { 384 return validationStatus; 385 } 386 Data data = dereference(validateContext); 387 calcDigestValue = transform(data, validateContext); 388 389 if (LOG.isDebugEnabled()) { 390 LOG.debug("Expected digest: " + XMLUtils.encodeToString(digestValue)); 391 LOG.debug("Actual digest: " + XMLUtils.encodeToString(calcDigestValue)); 392 } 393 394 validationStatus = Arrays.equals(digestValue, calcDigestValue); 395 validated = true; 396 return validationStatus; 397 } 398 getDereferencedData()399 public Data getDereferencedData() { 400 return derefData; 401 } 402 getDigestInputStream()403 public InputStream getDigestInputStream() { 404 return dis; 405 } 406 dereference(XMLCryptoContext context)407 private Data dereference(XMLCryptoContext context) 408 throws XMLSignatureException 409 { 410 Data data = null; 411 412 // use user-specified URIDereferencer if specified; otherwise use deflt 413 URIDereferencer deref = context.getURIDereferencer(); 414 if (deref == null) { 415 deref = DOMURIDereferencer.INSTANCE; 416 } 417 try { 418 data = deref.dereference(this, context); 419 LOG.debug("URIDereferencer class name: {}", deref.getClass().getName()); 420 LOG.debug("Data class name: {}", data.getClass().getName()); 421 } catch (URIReferenceException ure) { 422 throw new XMLSignatureException(ure); 423 } 424 425 return data; 426 } 427 transform(Data dereferencedData, XMLCryptoContext context)428 private byte[] transform(Data dereferencedData, 429 XMLCryptoContext context) 430 throws XMLSignatureException 431 { 432 if (md == null) { 433 try { 434 md = MessageDigest.getInstance 435 (((DOMDigestMethod)digestMethod).getMessageDigestAlgorithm()); 436 } catch (NoSuchAlgorithmException nsae) { 437 throw new XMLSignatureException(nsae); 438 } 439 } 440 md.reset(); 441 DigesterOutputStream dos; 442 Boolean cache = (Boolean) 443 context.getProperty("javax.xml.crypto.dsig.cacheReference"); 444 if (cache != null && cache) { 445 this.derefData = copyDerefData(dereferencedData); 446 dos = new DigesterOutputStream(md, true); 447 } else { 448 dos = new DigesterOutputStream(md); 449 } 450 Data data = dereferencedData; 451 try (OutputStream os = new UnsyncBufferedOutputStream(dos)) { 452 for (int i = 0, size = transforms.size(); i < size; i++) { 453 DOMTransform transform = (DOMTransform)transforms.get(i); 454 if (i < size - 1) { 455 data = transform.transform(data, context); 456 } else { 457 data = transform.transform(data, context, os); 458 } 459 } 460 461 if (data != null) { 462 XMLSignatureInput xi; 463 // explicitly use C14N 1.1 when generating signature 464 // first check system property, then context property 465 boolean c14n11 = useC14N11; 466 String c14nalg = CanonicalizationMethod.INCLUSIVE; 467 if (context instanceof XMLSignContext) { 468 if (!c14n11) { 469 Boolean prop = (Boolean)context.getProperty 470 ("com.sun.org.apache.xml.internal.security.useC14N11"); 471 c14n11 = prop != null && prop; 472 if (c14n11) { 473 c14nalg = "http://www.w3.org/2006/12/xml-c14n11"; 474 } 475 } else { 476 c14nalg = "http://www.w3.org/2006/12/xml-c14n11"; 477 } 478 } 479 if (data instanceof ApacheData) { 480 xi = ((ApacheData)data).getXMLSignatureInput(); 481 } else if (data instanceof OctetStreamData) { 482 xi = new XMLSignatureInput 483 (((OctetStreamData)data).getOctetStream()); 484 } else if (data instanceof NodeSetData) { 485 TransformService spi = null; 486 if (provider == null) { 487 spi = TransformService.getInstance(c14nalg, "DOM"); 488 } else { 489 try { 490 spi = TransformService.getInstance(c14nalg, "DOM", provider); 491 } catch (NoSuchAlgorithmException nsae) { 492 spi = TransformService.getInstance(c14nalg, "DOM"); 493 } 494 } 495 data = spi.transform(data, context); 496 xi = new XMLSignatureInput 497 (((OctetStreamData)data).getOctetStream()); 498 } else { 499 throw new XMLSignatureException("unrecognized Data type"); 500 } 501 502 boolean secVal = Utils.secureValidation(context); 503 xi.setSecureValidation(secVal); 504 if (context instanceof XMLSignContext && c14n11 505 && !xi.isOctetStream() && !xi.isOutputStreamSet()) { 506 TransformService spi = null; 507 if (provider == null) { 508 spi = TransformService.getInstance(c14nalg, "DOM"); 509 } else { 510 try { 511 spi = TransformService.getInstance(c14nalg, "DOM", provider); 512 } catch (NoSuchAlgorithmException nsae) { 513 spi = TransformService.getInstance(c14nalg, "DOM"); 514 } 515 } 516 517 DOMTransform t = new DOMTransform(spi); 518 Element transformsElem = null; 519 String dsPrefix = DOMUtils.getSignaturePrefix(context); 520 if (allTransforms.isEmpty()) { 521 transformsElem = DOMUtils.createElement( 522 refElem.getOwnerDocument(), 523 "Transforms", XMLSignature.XMLNS, dsPrefix); 524 refElem.insertBefore(transformsElem, 525 DOMUtils.getFirstChildElement(refElem)); 526 } else { 527 transformsElem = DOMUtils.getFirstChildElement(refElem); 528 } 529 t.marshal(transformsElem, dsPrefix, 530 (DOMCryptoContext)context); 531 allTransforms.add(t); 532 xi.updateOutputStream(os, true); 533 } else { 534 xi.updateOutputStream(os); 535 } 536 } 537 os.flush(); 538 if (cache != null && cache) { 539 this.dis = dos.getInputStream(); 540 } 541 return dos.getDigestValue(); 542 } catch (NoSuchAlgorithmException e) { 543 throw new XMLSignatureException(e); 544 } catch (TransformException e) { 545 throw new XMLSignatureException(e); 546 } catch (MarshalException e) { 547 throw new XMLSignatureException(e); 548 } catch (IOException e) { 549 throw new XMLSignatureException(e); 550 } catch (com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException e) { 551 throw new XMLSignatureException(e); 552 } finally { 553 if (dos != null) { 554 try { 555 dos.close(); 556 } catch (IOException e) { 557 throw new XMLSignatureException(e); 558 } 559 } 560 } 561 } 562 getHere()563 public Node getHere() { 564 return here; 565 } 566 567 @Override equals(Object o)568 public boolean equals(Object o) { 569 if (this == o) { 570 return true; 571 } 572 573 if (!(o instanceof Reference)) { 574 return false; 575 } 576 Reference oref = (Reference)o; 577 578 boolean idsEqual = id == null ? oref.getId() == null 579 : id.equals(oref.getId()); 580 boolean urisEqual = uri == null ? oref.getURI() == null 581 : uri.equals(oref.getURI()); 582 boolean typesEqual = type == null ? oref.getType() == null 583 : type.equals(oref.getType()); 584 boolean digestValuesEqual = 585 Arrays.equals(digestValue, oref.getDigestValue()); 586 587 return digestMethod.equals(oref.getDigestMethod()) && idsEqual && 588 urisEqual && typesEqual && 589 allTransforms.equals(oref.getTransforms()) && digestValuesEqual; 590 } 591 592 @Override hashCode()593 public int hashCode() { 594 int result = 17; 595 if (id != null) { 596 result = 31 * result + id.hashCode(); 597 } 598 if (uri != null) { 599 result = 31 * result + uri.hashCode(); 600 } 601 if (type != null) { 602 result = 31 * result + type.hashCode(); 603 } 604 if (digestValue != null) { 605 result = 31 * result + Arrays.hashCode(digestValue); 606 } 607 result = 31 * result + digestMethod.hashCode(); 608 result = 31 * result + allTransforms.hashCode(); 609 610 return result; 611 } 612 isDigested()613 boolean isDigested() { 614 return digested; 615 } 616 copyDerefData(Data dereferencedData)617 private static Data copyDerefData(Data dereferencedData) { 618 if (dereferencedData instanceof ApacheData) { 619 // need to make a copy of the Data 620 ApacheData ad = (ApacheData)dereferencedData; 621 XMLSignatureInput xsi = ad.getXMLSignatureInput(); 622 if (xsi.isNodeSet()) { 623 try { 624 final Set<Node> s = xsi.getNodeSet(); 625 return new NodeSetData<Node>() { 626 public Iterator<Node> iterator() { return s.iterator(); } 627 }; 628 } catch (Exception e) { 629 // LOG a warning 630 LOG.warn("cannot cache dereferenced data: " + e); 631 return null; 632 } 633 } else if (xsi.isElement()) { 634 return new DOMSubTreeData 635 (xsi.getSubNode(), xsi.isExcludeComments()); 636 } else if (xsi.isOctetStream() || xsi.isByteArray()) { 637 try { 638 return new OctetStreamData 639 (xsi.getOctetStream(), xsi.getSourceURI(), 640 xsi.getMIMEType()); 641 } catch (IOException ioe) { 642 // LOG a warning 643 LOG.warn("cannot cache dereferenced data: " + ioe); 644 return null; 645 } 646 } 647 } 648 return dereferencedData; 649 } 650 } 651