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, 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 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 manRefs = man.getReferences(); 475 for (int i = 0, size = manRefs.size(); i < size; i++) { 476 digestReference((DOMReference)manRefs.get(i), 477 signContext); 478 } 479 } 480 } 481 // if uri="" and there are XPath Transforms, there may be 482 // reference dependencies in the XPath Transform - so be on 483 // the safe side, and skip and do at end in the final sweep 484 if (uri.length() == 0) { 485 @SuppressWarnings("unchecked") 486 List<Transform> transforms = ref.getTransforms(); 487 for (Transform transform : transforms) { 488 String transformAlg = transform.getAlgorithm(); 489 if (transformAlg.equals(Transform.XPATH) || 490 transformAlg.equals(Transform.XPATH2)) { 491 return; 492 } 493 } 494 } 495 } 496 ref.digest(signContext); 497 } 498 499 public class DOMSignatureValue extends DOMStructure 500 implements SignatureValue 501 { 502 private String id; 503 private byte[] value; 504 private String valueBase64; 505 private Element sigValueElem; 506 private boolean validated = false; 507 private boolean validationStatus; 508 509 DOMSignatureValue(String id) { 510 this.id = id; 511 } 512 513 DOMSignatureValue(Element sigValueElem, XMLCryptoContext context) 514 throws MarshalException 515 { 516 try { 517 // base64 decode signatureValue 518 value = Base64.decode(sigValueElem); 519 } catch (Base64DecodingException bde) { 520 throw new MarshalException(bde); 521 } 522 523 Attr attr = sigValueElem.getAttributeNodeNS(null, "Id"); 524 if (attr != null) { 525 id = attr.getValue(); 526 sigValueElem.setIdAttributeNode(attr, true); 527 } else { 528 id = null; 529 } 530 this.sigValueElem = sigValueElem; 531 } 532 533 public String getId() { 534 return id; 535 } 536 537 public byte[] getValue() { 538 return (value == null) ? null : (byte[])value.clone(); 539 } 540 541 public boolean validate(XMLValidateContext validateContext) 542 throws XMLSignatureException 543 { 544 if (validateContext == null) { 545 throw new NullPointerException("context cannot be null"); 546 } 547 548 if (validated) { 549 return validationStatus; 550 } 551 552 // get validating key 553 SignatureMethod sm = si.getSignatureMethod(); 554 Key validationKey = null; 555 KeySelectorResult ksResult; 556 try { 557 ksResult = validateContext.getKeySelector().select 558 (ki, KeySelector.Purpose.VERIFY, sm, validateContext); 559 validationKey = ksResult.getKey(); 560 if (validationKey == null) { 561 throw new XMLSignatureException("the keyselector did not " + 562 "find a validation key"); 563 } 564 } catch (KeySelectorException kse) { 565 throw new XMLSignatureException("cannot find validation " + 566 "key", kse); 567 } 568 569 // canonicalize SignedInfo and verify signature 570 try { 571 validationStatus = ((AbstractDOMSignatureMethod)sm).verify 572 (validationKey, si, value, validateContext); 573 } catch (Exception e) { 574 throw new XMLSignatureException(e); 575 } 576 577 validated = true; 578 ksr = ksResult; 579 return validationStatus; 580 } 581 582 @Override 583 public boolean equals(Object o) { 584 if (this == o) { 585 return true; 586 } 587 588 if (!(o instanceof SignatureValue)) { 589 return false; 590 } 591 SignatureValue osv = (SignatureValue)o; 592 593 boolean idEqual = 594 (id == null ? osv.getId() == null : id.equals(osv.getId())); 595 596 //XXX compare signature values? 597 return idEqual; 598 } 599 600 @Override 601 public int hashCode() { 602 int result = 17; 603 if (id != null) { 604 result = 31 * result + id.hashCode(); 605 } 606 607 return result; 608 } 609 610 public void marshal(Node parent, String dsPrefix, 611 DOMCryptoContext context) 612 throws MarshalException 613 { 614 // create SignatureValue element 615 sigValueElem = DOMUtils.createElement(ownerDoc, "SignatureValue", 616 XMLSignature.XMLNS, dsPrefix); 617 if (valueBase64 != null) { 618 sigValueElem.appendChild(ownerDoc.createTextNode(valueBase64)); 619 } 620 621 // append Id attribute, if specified 622 DOMUtils.setAttributeID(sigValueElem, "Id", id); 623 parent.appendChild(sigValueElem); 624 } 625 626 void setValue(byte[] value) { 627 this.value = value; 628 valueBase64 = Base64.encode(value); 629 sigValueElem.appendChild(ownerDoc.createTextNode(valueBase64)); 630 } 631 } 632 }