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