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 }