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