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