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