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 }