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: DOMXMLSignature.java 1333415 2012-05-03 12:03:51Z coheigea $ 35 */ 36 package org.jcp.xml.dsig.internal.dom; 37 38 import javax.xml.crypto.*; 39 import javax.xml.crypto.dom.*; 40 import javax.xml.crypto.dsig.*; 41 import javax.xml.crypto.dsig.dom.DOMSignContext; 42 import javax.xml.crypto.dsig.dom.DOMValidateContext; 43 import javax.xml.crypto.dsig.keyinfo.KeyInfo; 44 45 import java.security.InvalidKeyException; 46 import java.security.Key; 47 import java.security.Provider; 48 import java.util.Collections; 49 import java.util.ArrayList; 50 import java.util.HashMap; 51 import java.util.List; 52 53 import org.w3c.dom.Attr; 54 import org.w3c.dom.Document; 55 import org.w3c.dom.Element; 56 import org.w3c.dom.Node; 57 58 import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException; 59 import com.sun.org.apache.xml.internal.security.utils.Base64; 60 61 /** 62 * DOM-based implementation of XMLSignature. 63 * 64 * @author Sean Mullan 65 * @author Joyce Leung 66 */ 67 public final class DOMXMLSignature extends DOMStructure 68 implements XMLSignature { 69 70 private static java.util.logging.Logger log = 71 java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom"); 72 private String id; 73 private SignatureValue sv; 74 private KeyInfo ki; 75 private List<XMLObject> objects; 76 private SignedInfo si; 77 private Document ownerDoc = null; 78 private Element localSigElem = null; 79 private Element sigElem = null; 80 private boolean validationStatus; 81 private boolean validated = false; 82 private KeySelectorResult ksr; 83 private HashMap<String, XMLStructure> signatureIdMap; 84 85 static { 86 com.sun.org.apache.xml.internal.security.Init.init(); 87 } 88 89 /** 90 * Creates a <code>DOMXMLSignature</code> from the specified components. 91 * 92 * @param si the <code>SignedInfo</code> 93 * @param ki the <code>KeyInfo</code>, or <code>null</code> if not specified 94 * @param objs a list of <code>XMLObject</code>s or <code>null</code> 95 * if not specified. The list is copied to protect against subsequent 96 * modification. 97 * @param id an optional id (specify <code>null</code> to omit) 98 * @param signatureValueId an optional id (specify <code>null</code> to 99 * omit) 100 * @throws NullPointerException if <code>si</code> is <code>null</code> 101 */ 102 public DOMXMLSignature(SignedInfo si, KeyInfo ki, 103 List<? extends XMLObject> objs, 104 String id, String signatureValueId) 105 { 106 if (si == null) { 107 throw new NullPointerException("signedInfo cannot be null"); 108 } 109 this.si = si; 110 this.id = id; 111 this.sv = new DOMSignatureValue(signatureValueId); 112 if (objs == null) { 113 this.objects = Collections.emptyList(); 114 } else { 115 this.objects = 116 Collections.unmodifiableList(new ArrayList<XMLObject>(objs)); 117 for (int i = 0, size = this.objects.size(); i < size; i++) { 118 if (!(this.objects.get(i) instanceof XMLObject)) { 119 throw new ClassCastException 120 ("objs["+i+"] is not an XMLObject"); 121 } 122 } 123 } 124 this.ki = ki; 125 } 126 127 /** 128 * Creates a <code>DOMXMLSignature</code> from XML. 129 * 130 * @param sigElem Signature element 131 * @throws MarshalException if XMLSignature cannot be unmarshalled 132 */ 133 public DOMXMLSignature(Element sigElem, XMLCryptoContext context, 134 Provider provider) 135 throws MarshalException 136 { 137 localSigElem = sigElem; 138 ownerDoc = localSigElem.getOwnerDocument(); 139 140 // get Id attribute, if specified 141 id = DOMUtils.getAttributeValue(localSigElem, "Id"); 142 143 // unmarshal SignedInfo 144 Element siElem = DOMUtils.getFirstChildElement(localSigElem, 145 "SignedInfo"); 146 si = new DOMSignedInfo(siElem, context, provider); 147 148 // unmarshal SignatureValue 149 Element sigValElem = DOMUtils.getNextSiblingElement(siElem, 150 "SignatureValue"); 151 sv = new DOMSignatureValue(sigValElem, context); 152 153 // unmarshal KeyInfo, if specified 154 Element nextSibling = DOMUtils.getNextSiblingElement(sigValElem); 155 if (nextSibling != null && nextSibling.getLocalName().equals("KeyInfo")) { 156 ki = new DOMKeyInfo(nextSibling, context, provider); 157 nextSibling = DOMUtils.getNextSiblingElement(nextSibling); 158 } 159 160 // unmarshal Objects, if specified 161 if (nextSibling == null) { 162 objects = Collections.emptyList(); 163 } else { 164 List<XMLObject> tempObjects = new ArrayList<XMLObject>(); 165 while (nextSibling != null) { 166 String name = nextSibling.getLocalName(); 167 if (!name.equals("Object")) { 168 throw new MarshalException("Invalid element name: " + name + 169 ", expected KeyInfo or Object"); 170 } 171 tempObjects.add(new DOMXMLObject(nextSibling, 172 context, provider)); 173 nextSibling = DOMUtils.getNextSiblingElement(nextSibling); 174 } 175 objects = Collections.unmodifiableList(tempObjects); 176 } 177 } 178 179 public String getId() { 180 return id; 181 } 182 183 public KeyInfo getKeyInfo() { 184 return ki; 185 } 186 187 public SignedInfo getSignedInfo() { 188 return si; 189 } 190 191 public List<XMLObject> getObjects() { 192 return objects; 193 } 194 195 public SignatureValue getSignatureValue() { 196 return sv; 197 } 198 199 public KeySelectorResult getKeySelectorResult() { 200 return ksr; 201 } 202 203 public void marshal(Node parent, String dsPrefix, DOMCryptoContext context) 204 throws MarshalException 205 { 206 marshal(parent, null, dsPrefix, context); 207 } 208 209 public void marshal(Node parent, Node nextSibling, String dsPrefix, 210 DOMCryptoContext context) 211 throws MarshalException 212 { 213 ownerDoc = DOMUtils.getOwnerDocument(parent); 214 sigElem = DOMUtils.createElement(ownerDoc, "Signature", 215 XMLSignature.XMLNS, dsPrefix); 216 217 // append xmlns attribute 218 if (dsPrefix == null || dsPrefix.length() == 0) { 219 sigElem.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", 220 XMLSignature.XMLNS); 221 } else { 222 sigElem.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:" + 223 dsPrefix, XMLSignature.XMLNS); 224 } 225 226 // create and append SignedInfo element 227 ((DOMSignedInfo)si).marshal(sigElem, dsPrefix, context); 228 229 // create and append SignatureValue element 230 ((DOMSignatureValue)sv).marshal(sigElem, dsPrefix, context); 231 232 // create and append KeyInfo element if necessary 233 if (ki != null) { 234 ((DOMKeyInfo)ki).marshal(sigElem, null, dsPrefix, context); 235 } 236 237 // create and append Object elements if necessary 238 for (int i = 0, size = objects.size(); i < size; i++) { 239 ((DOMXMLObject)objects.get(i)).marshal(sigElem, dsPrefix, context); 240 } 241 242 // append Id attribute 243 DOMUtils.setAttributeID(sigElem, "Id", id); 244 245 parent.insertBefore(sigElem, nextSibling); 246 } 247 248 public boolean validate(XMLValidateContext vc) 249 throws XMLSignatureException 250 { 251 if (vc == null) { 252 throw new NullPointerException("validateContext is null"); 253 } 254 255 if (!(vc instanceof DOMValidateContext)) { 256 throw new ClassCastException 257 ("validateContext must be of type DOMValidateContext"); 258 } 259 260 if (validated) { 261 return validationStatus; 262 } 263 264 // validate the signature 265 boolean sigValidity = sv.validate(vc); 266 if (!sigValidity) { 267 validationStatus = false; 268 validated = true; 269 return validationStatus; 270 } 271 272 // validate all References 273 @SuppressWarnings("unchecked") 274 List<Reference> refs = this.si.getReferences(); 275 boolean validateRefs = true; 276 for (int i = 0, size = refs.size(); validateRefs && i < size; i++) { 277 Reference ref = refs.get(i); 278 boolean refValid = ref.validate(vc); 279 if (log.isLoggable(java.util.logging.Level.FINE)) { 280 log.log(java.util.logging.Level.FINE, "Reference[" + ref.getURI() + "] is valid: " + refValid); 281 } 282 validateRefs &= refValid; 283 } 284 if (!validateRefs) { 285 if (log.isLoggable(java.util.logging.Level.FINE)) { 286 log.log(java.util.logging.Level.FINE, "Couldn't validate the References"); 287 } 288 validationStatus = false; 289 validated = true; 290 return validationStatus; 291 } 292 293 // validate Manifests, if property set 294 boolean validateMans = true; 295 if (Boolean.TRUE.equals(vc.getProperty 296 ("org.jcp.xml.dsig.validateManifests"))) 297 { 298 for (int i=0, size=objects.size(); validateMans && i < size; i++) { 299 XMLObject xo = objects.get(i); 300 @SuppressWarnings("unchecked") 301 List<XMLStructure> content = xo.getContent(); 302 int csize = content.size(); 303 for (int j = 0; validateMans && j < csize; j++) { 304 XMLStructure xs = content.get(j); 305 if (xs instanceof Manifest) { 306 if (log.isLoggable(java.util.logging.Level.FINE)) { 307 log.log(java.util.logging.Level.FINE, "validating manifest"); 308 } 309 Manifest man = (Manifest)xs; 310 @SuppressWarnings("unchecked") 311 List<Reference> manRefs = man.getReferences(); 312 int rsize = manRefs.size(); 313 for (int k = 0; validateMans && k < rsize; k++) { 314 Reference ref = manRefs.get(k); 315 boolean refValid = ref.validate(vc); 316 if (log.isLoggable(java.util.logging.Level.FINE)) { 317 log.log(java.util.logging.Level.FINE, 318 "Manifest ref[" + ref.getURI() + "] is valid: " + refValid 319 ); 320 } 321 validateMans &= refValid; 322 } 323 } 324 } 325 } 326 } 327 328 validationStatus = validateMans; 329 validated = true; 330 return validationStatus; 331 } 332 333 public void sign(XMLSignContext signContext) 334 throws MarshalException, XMLSignatureException 335 { 336 if (signContext == null) { 337 throw new NullPointerException("signContext cannot be null"); 338 } 339 DOMSignContext context = (DOMSignContext)signContext; 340 marshal(context.getParent(), context.getNextSibling(), 341 DOMUtils.getSignaturePrefix(context), context); 342 343 // generate references and signature value 344 List<Reference> allReferences = new ArrayList<Reference>(); 345 346 // traverse the Signature and register all objects with IDs that 347 // may contain References 348 signatureIdMap = new HashMap<String, XMLStructure>(); 349 signatureIdMap.put(id, this); 350 signatureIdMap.put(si.getId(), si); 351 @SuppressWarnings("unchecked") 352 List<Reference> refs = si.getReferences(); 353 for (Reference ref : refs) { 354 signatureIdMap.put(ref.getId(), ref); 355 } 356 for (XMLObject obj : objects) { 357 signatureIdMap.put(obj.getId(), obj); 358 @SuppressWarnings("unchecked") 359 List<XMLStructure> content = obj.getContent(); 360 for (XMLStructure xs : content) { 361 if (xs instanceof Manifest) { 362 Manifest man = (Manifest)xs; 363 signatureIdMap.put(man.getId(), man); 364 @SuppressWarnings("unchecked") 365 List<Reference> manRefs = man.getReferences(); 366 for (Reference ref : manRefs) { 367 allReferences.add(ref); 368 signatureIdMap.put(ref.getId(), ref); 369 } 370 } 371 } 372 } 373 // always add SignedInfo references after Manifest references so 374 // that Manifest reference are digested first 375 allReferences.addAll(refs); 376 377 // generate/digest each reference 378 for (Reference ref : allReferences) { 379 digestReference((DOMReference)ref, signContext); 380 } 381 382 // do final sweep to digest any references that were skipped or missed 383 for (Reference ref : allReferences) { 384 if (((DOMReference)ref).isDigested()) { 385 continue; 386 } 387 ((DOMReference)ref).digest(signContext); 388 } 389 390 Key signingKey = null; 391 KeySelectorResult ksr = null; 392 try { 393 ksr = signContext.getKeySelector().select(ki, 394 KeySelector.Purpose.SIGN, 395 si.getSignatureMethod(), 396 signContext); 397 signingKey = ksr.getKey(); 398 if (signingKey == null) { 399 throw new XMLSignatureException("the keySelector did not " + 400 "find a signing key"); 401 } 402 } catch (KeySelectorException kse) { 403 throw new XMLSignatureException("cannot find signing key", kse); 404 } 405 406 // calculate signature value 407 try { 408 byte[] val = ((AbstractDOMSignatureMethod) 409 si.getSignatureMethod()).sign(signingKey, si, signContext); 410 ((DOMSignatureValue)sv).setValue(val); 411 } catch (InvalidKeyException ike) { 412 throw new XMLSignatureException(ike); 413 } 414 415 this.localSigElem = sigElem; 416 this.ksr = ksr; 417 } 418 419 @Override 420 public boolean equals(Object o) { 421 if (this == o) { 422 return true; 423 } 424 425 if (!(o instanceof XMLSignature)) { 426 return false; 427 } 428 XMLSignature osig = (XMLSignature)o; 429 430 boolean idEqual = 431 (id == null ? osig.getId() == null : id.equals(osig.getId())); 432 boolean keyInfoEqual = 433 (ki == null ? osig.getKeyInfo() == null 434 : ki.equals(osig.getKeyInfo())); 435 436 return (idEqual && keyInfoEqual && 437 sv.equals(osig.getSignatureValue()) && 438 si.equals(osig.getSignedInfo()) && 439 objects.equals(osig.getObjects())); 440 } 441 442 @Override 443 public int hashCode() { 444 int result = 17; 445 if (id != null) { 446 result = 31 * result + id.hashCode(); 447 } 448 if (ki != null) { 449 result = 31 * result + ki.hashCode(); 450 } 451 result = 31 * result + sv.hashCode(); 452 result = 31 * result + si.hashCode(); 453 result = 31 * result + objects.hashCode(); 454 455 return result; 456 } 457 458 private void digestReference(DOMReference ref, XMLSignContext signContext) 459 throws XMLSignatureException 460 { 461 if (ref.isDigested()) { 462 return; 463 } 464 // check dependencies 465 String uri = ref.getURI(); 466 if (Utils.sameDocumentURI(uri)) { 467 String id = Utils.parseIdFromSameDocumentURI(uri); 468 if (id != null && signatureIdMap.containsKey(id)) { 469 XMLStructure xs = signatureIdMap.get(id); 470 if (xs instanceof DOMReference) { 471 digestReference((DOMReference)xs, signContext); 472 } else if (xs instanceof Manifest) { 473 Manifest man = (Manifest)xs; 474 List<Reference> manRefs = 475 DOMManifest.getManifestReferences(man); 476 for (int i = 0, size = manRefs.size(); i < size; i++) { 477 digestReference((DOMReference)manRefs.get(i), 478 signContext); 479 } 480 } 481 } 482 // if uri="" and there are XPath Transforms, there may be 483 // reference dependencies in the XPath Transform - so be on 484 // the safe side, and skip and do at end in the final sweep 485 if (uri.length() == 0) { 486 @SuppressWarnings("unchecked") 487 List<Transform> transforms = ref.getTransforms(); 488 for (Transform transform : transforms) { 489 String transformAlg = transform.getAlgorithm(); 490 if (transformAlg.equals(Transform.XPATH) || 491 transformAlg.equals(Transform.XPATH2)) { 492 return; 493 } 494 } 495 } 496 } 497 ref.digest(signContext); 498 } 499 500 public class DOMSignatureValue extends DOMStructure 501 implements SignatureValue 502 { 503 private String id; 504 private byte[] value; 505 private String valueBase64; 506 private Element sigValueElem; 507 private boolean validated = false; 508 private boolean validationStatus; 509 510 DOMSignatureValue(String id) { 511 this.id = id; 512 } 513 514 DOMSignatureValue(Element sigValueElem, XMLCryptoContext context) 515 throws MarshalException 516 { 517 try { 518 // base64 decode signatureValue 519 value = Base64.decode(sigValueElem); 520 } catch (Base64DecodingException bde) { 521 throw new MarshalException(bde); 522 } 523 524 Attr attr = sigValueElem.getAttributeNodeNS(null, "Id"); 525 if (attr != null) { 526 id = attr.getValue(); 527 sigValueElem.setIdAttributeNode(attr, true); 528 } else { 529 id = null; 530 } 531 this.sigValueElem = sigValueElem; 532 } 533 534 public String getId() { 535 return id; 536 } 537 538 public byte[] getValue() { 539 return (value == null) ? null : value.clone(); 540 } 541 542 public boolean validate(XMLValidateContext validateContext) 543 throws XMLSignatureException 544 { 545 if (validateContext == null) { 546 throw new NullPointerException("context cannot be null"); 547 } 548 549 if (validated) { 550 return validationStatus; 551 } 552 553 // get validating key 554 SignatureMethod sm = si.getSignatureMethod(); 555 Key validationKey = null; 556 KeySelectorResult ksResult; 557 try { 558 ksResult = validateContext.getKeySelector().select 559 (ki, KeySelector.Purpose.VERIFY, sm, validateContext); 560 validationKey = ksResult.getKey(); 561 if (validationKey == null) { 562 throw new XMLSignatureException("the keyselector did not " + 563 "find a validation key"); 564 } 565 } catch (KeySelectorException kse) { 566 throw new XMLSignatureException("cannot find validation " + 567 "key", kse); 568 } 569 570 // canonicalize SignedInfo and verify signature 571 try { 572 validationStatus = ((AbstractDOMSignatureMethod)sm).verify 573 (validationKey, si, value, validateContext); 574 } catch (Exception e) { 575 throw new XMLSignatureException(e); 576 } 577 578 validated = true; 579 ksr = ksResult; 580 return validationStatus; 581 } 582 583 @Override 584 public boolean equals(Object o) { 585 if (this == o) { 586 return true; 587 } 588 589 if (!(o instanceof SignatureValue)) { 590 return false; 591 } 592 SignatureValue osv = (SignatureValue)o; 593 594 boolean idEqual = 595 (id == null ? osv.getId() == null : id.equals(osv.getId())); 596 597 //XXX compare signature values? 598 return idEqual; 599 } 600 601 @Override 602 public int hashCode() { 603 int result = 17; 604 if (id != null) { 605 result = 31 * result + id.hashCode(); 606 } 607 608 return result; 609 } 610 611 public void marshal(Node parent, String dsPrefix, 612 DOMCryptoContext context) 613 throws MarshalException 614 { 615 // create SignatureValue element 616 sigValueElem = DOMUtils.createElement(ownerDoc, "SignatureValue", 617 XMLSignature.XMLNS, dsPrefix); 618 if (valueBase64 != null) { 619 sigValueElem.appendChild(ownerDoc.createTextNode(valueBase64)); 620 } 621 622 // append Id attribute, if specified 623 DOMUtils.setAttributeID(sigValueElem, "Id", id); 624 parent.appendChild(sigValueElem); 625 } 626 627 void setValue(byte[] value) { 628 this.value = value; 629 valueBase64 = Base64.encode(value); 630 sigValueElem.appendChild(ownerDoc.createTextNode(valueBase64)); 631 } 632 } 633 }