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