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  * $Id: DOMSignedInfo.java 1333415 2012-05-03 12:03:51Z coheigea $
  28  */
  29 package org.jcp.xml.dsig.internal.dom;
  30 
  31 import javax.xml.crypto.*;
  32 import javax.xml.crypto.dom.DOMCryptoContext;
  33 import javax.xml.crypto.dsig.*;
  34 
  35 import java.io.ByteArrayInputStream;
  36 import java.io.ByteArrayOutputStream;
  37 import java.io.InputStream;
  38 import java.io.OutputStream;
  39 import java.io.IOException;
  40 import java.security.Provider;
  41 import java.util.*;
  42 
  43 import org.w3c.dom.Document;
  44 import org.w3c.dom.Element;
  45 import org.w3c.dom.Node;
  46 
  47 import com.sun.org.apache.xml.internal.security.utils.Base64;
  48 import com.sun.org.apache.xml.internal.security.utils.Constants;
  49 import com.sun.org.apache.xml.internal.security.utils.UnsyncBufferedOutputStream;
  50 
  51 /**
  52  * DOM-based implementation of SignedInfo.
  53  *
  54  * @author Sean Mullan
  55  */
  56 public final class DOMSignedInfo extends DOMStructure implements SignedInfo {
  57 
  58     /**
  59      * The maximum number of references per Manifest, if secure validation is enabled.
  60      */
  61     public static final int MAXIMUM_REFERENCE_COUNT = 30;
  62 
  63     private static java.util.logging.Logger log =
  64         java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom");
  65 
  66     /** Signature - NOT Recommended RSAwithMD5 */
  67     private static final String ALGO_ID_SIGNATURE_NOT_RECOMMENDED_RSA_MD5 =
  68         Constants.MoreAlgorithmsSpecNS + "rsa-md5";
  69 
  70     /** HMAC - NOT Recommended HMAC-MD5 */
  71     private static final String ALGO_ID_MAC_HMAC_NOT_RECOMMENDED_MD5 =
  72         Constants.MoreAlgorithmsSpecNS + "hmac-md5";
  73 
  74     private List<Reference> references;
  75     private CanonicalizationMethod canonicalizationMethod;
  76     private SignatureMethod signatureMethod;
  77     private String id;
  78     private Document ownerDoc;
  79     private Element localSiElem;
  80     private InputStream canonData;
  81 
  82     /**
  83      * Creates a <code>DOMSignedInfo</code> from the specified parameters. Use
  84      * this constructor when the <code>Id</code> is not specified.
  85      *
  86      * @param cm the canonicalization method
  87      * @param sm the signature method
  88      * @param references the list of references. The list is copied.
  89      * @throws NullPointerException if
  90      *    <code>cm</code>, <code>sm</code>, or <code>references</code> is
  91      *    <code>null</code>
  92      * @throws IllegalArgumentException if <code>references</code> is empty
  93      * @throws ClassCastException if any of the references are not of
  94      *    type <code>Reference</code>
  95      */
  96     public DOMSignedInfo(CanonicalizationMethod cm, SignatureMethod sm,
  97                          List<? extends Reference> references) {
  98         if (cm == null || sm == null || references == null) {
  99             throw new NullPointerException();
 100         }
 101         this.canonicalizationMethod = cm;
 102         this.signatureMethod = sm;
 103         this.references = Collections.unmodifiableList(
 104             new ArrayList<Reference>(references));
 105         if (this.references.isEmpty()) {
 106             throw new IllegalArgumentException("list of references must " +
 107                 "contain at least one entry");
 108         }
 109         for (int i = 0, size = this.references.size(); i < size; i++) {
 110             Object obj = this.references.get(i);
 111             if (!(obj instanceof Reference)) {
 112                 throw new ClassCastException("list of references contains " +
 113                     "an illegal type");
 114             }
 115         }
 116     }
 117 
 118     /**
 119      * Creates a <code>DOMSignedInfo</code> from the specified parameters.
 120      *
 121      * @param cm the canonicalization method
 122      * @param sm the signature method
 123      * @param references the list of references. The list is copied.
 124      * @param id an optional identifer that will allow this
 125      *    <code>SignedInfo</code> to be referenced by other signatures and
 126      *    objects
 127      * @throws NullPointerException if <code>cm</code>, <code>sm</code>,
 128      *    or <code>references</code> is <code>null</code>
 129      * @throws IllegalArgumentException if <code>references</code> is empty
 130      * @throws ClassCastException if any of the references are not of
 131      *    type <code>Reference</code>
 132      */
 133     public DOMSignedInfo(CanonicalizationMethod cm, SignatureMethod sm,
 134                          List<? extends Reference> references, String id) {
 135         this(cm, sm, references);
 136         this.id = id;
 137     }
 138 
 139     /**
 140      * Creates a <code>DOMSignedInfo</code> from an element.
 141      *
 142      * @param siElem a SignedInfo element
 143      */
 144     public DOMSignedInfo(Element siElem, XMLCryptoContext context, Provider provider)
 145         throws MarshalException {
 146         localSiElem = siElem;
 147         ownerDoc = siElem.getOwnerDocument();
 148 
 149         // get Id attribute, if specified
 150         id = DOMUtils.getAttributeValue(siElem, "Id");
 151 
 152         // unmarshal CanonicalizationMethod
 153         Element cmElem = DOMUtils.getFirstChildElement(siElem,
 154                                                        "CanonicalizationMethod");
 155         canonicalizationMethod = new DOMCanonicalizationMethod(cmElem, context,
 156                                                                provider);
 157 
 158         // unmarshal SignatureMethod
 159         Element smElem = DOMUtils.getNextSiblingElement(cmElem,
 160                                                         "SignatureMethod");
 161         signatureMethod = DOMSignatureMethod.unmarshal(smElem);
 162 
 163         boolean secVal = Utils.secureValidation(context);
 164 
 165         String signatureMethodAlgorithm = signatureMethod.getAlgorithm();
 166         if (secVal && ((ALGO_ID_MAC_HMAC_NOT_RECOMMENDED_MD5.equals(signatureMethodAlgorithm)
 167                 || ALGO_ID_SIGNATURE_NOT_RECOMMENDED_RSA_MD5.equals(signatureMethodAlgorithm)))) {
 168             throw new MarshalException(
 169                 "It is forbidden to use algorithm " + signatureMethod + " when secure validation is enabled"
 170             );
 171         }
 172 
 173         // unmarshal References
 174         ArrayList<Reference> refList = new ArrayList<Reference>(5);
 175         Element refElem = DOMUtils.getNextSiblingElement(smElem, "Reference");
 176         refList.add(new DOMReference(refElem, context, provider));
 177 
 178         refElem = DOMUtils.getNextSiblingElement(refElem);
 179         while (refElem != null) {
 180             String name = refElem.getLocalName();
 181             if (!name.equals("Reference")) {
 182                 throw new MarshalException("Invalid element name: " +
 183                                            name + ", expected Reference");
 184             }
 185             refList.add(new DOMReference(refElem, context, provider));
 186 
 187             if (secVal && (refList.size() > MAXIMUM_REFERENCE_COUNT)) {
 188                 String error = "A maxiumum of " + MAXIMUM_REFERENCE_COUNT + " "
 189                     + "references per Manifest are allowed with secure validation";
 190                 throw new MarshalException(error);
 191             }
 192             refElem = DOMUtils.getNextSiblingElement(refElem);
 193         }
 194         references = Collections.unmodifiableList(refList);
 195     }
 196 
 197     public CanonicalizationMethod getCanonicalizationMethod() {
 198         return canonicalizationMethod;
 199     }
 200 
 201     public SignatureMethod getSignatureMethod() {
 202         return signatureMethod;
 203     }
 204 
 205     public String getId() {
 206         return id;
 207     }
 208 
 209     public List getReferences() {
 210         return references;
 211     }
 212 
 213     public InputStream getCanonicalizedData() {
 214         return canonData;
 215     }
 216 
 217     public void canonicalize(XMLCryptoContext context, ByteArrayOutputStream bos)
 218         throws XMLSignatureException {
 219         if (context == null) {
 220             throw new NullPointerException("context cannot be null");
 221         }
 222 
 223         OutputStream os = new UnsyncBufferedOutputStream(bos);
 224         try {
 225             os.close();
 226         } catch (IOException e) {
 227             if (log.isLoggable(java.util.logging.Level.FINE)) {
 228                 log.log(java.util.logging.Level.FINE, e.getMessage(), e);
 229             }
 230             // Impossible
 231         }
 232 
 233         DOMSubTreeData subTree = new DOMSubTreeData(localSiElem, true);
 234 
 235         try {
 236             ((DOMCanonicalizationMethod)
 237                 canonicalizationMethod).canonicalize(subTree, context, bos);
 238         } catch (TransformException te) {
 239             throw new XMLSignatureException(te);
 240         }
 241 
 242         byte[] signedInfoBytes = bos.toByteArray();
 243 
 244         // this whole block should only be done if logging is enabled
 245         if (log.isLoggable(java.util.logging.Level.FINE)) {
 246             log.log(java.util.logging.Level.FINE, "Canonicalized SignedInfo:");
 247             StringBuilder sb = new StringBuilder(signedInfoBytes.length);
 248             for (int i = 0; i < signedInfoBytes.length; i++) {
 249                 sb.append((char)signedInfoBytes[i]);
 250             }
 251             log.log(java.util.logging.Level.FINE, sb.toString());
 252             log.log(java.util.logging.Level.FINE, "Data to be signed/verified:" + Base64.encode(signedInfoBytes));
 253         }
 254 
 255         this.canonData = new ByteArrayInputStream(signedInfoBytes);
 256     }
 257 
 258     public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
 259         throws MarshalException
 260     {
 261         ownerDoc = DOMUtils.getOwnerDocument(parent);
 262         Element siElem = DOMUtils.createElement(ownerDoc, "SignedInfo",
 263                                                 XMLSignature.XMLNS, dsPrefix);
 264 
 265         // create and append CanonicalizationMethod element
 266         DOMCanonicalizationMethod dcm =
 267             (DOMCanonicalizationMethod)canonicalizationMethod;
 268         dcm.marshal(siElem, dsPrefix, context);
 269 
 270         // create and append SignatureMethod element
 271         ((DOMStructure)signatureMethod).marshal(siElem, dsPrefix, context);
 272 
 273         // create and append Reference elements
 274         for (Reference reference : references) {
 275             ((DOMReference)reference).marshal(siElem, dsPrefix, context);
 276         }
 277 
 278         // append Id attribute
 279         DOMUtils.setAttributeID(siElem, "Id", id);
 280 
 281         parent.appendChild(siElem);
 282         localSiElem = siElem;
 283     }
 284 
 285     @Override
 286     public boolean equals(Object o) {
 287         if (this == o) {
 288             return true;
 289         }
 290 
 291         if (!(o instanceof SignedInfo)) {
 292             return false;
 293         }
 294         SignedInfo osi = (SignedInfo)o;
 295 
 296         boolean idEqual = (id == null ? osi.getId() == null
 297                                       : id.equals(osi.getId()));
 298 
 299         return (canonicalizationMethod.equals(osi.getCanonicalizationMethod())
 300                 && signatureMethod.equals(osi.getSignatureMethod()) &&
 301                 references.equals(osi.getReferences()) && idEqual);
 302     }
 303 
 304     @Override
 305     public int hashCode() {
 306         int result = 17;
 307         if (id != null) {
 308             result = 31 * result + id.hashCode();
 309         }
 310         result = 31 * result + canonicalizationMethod.hashCode();
 311         result = 31 * result + signatureMethod.hashCode();
 312         result = 31 * result + references.hashCode();
 313 
 314         return result;
 315     }
 316 }