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: DOMRetrievalMethod.java 1333415 2012-05-03 12:03:51Z coheigea $
  35  */
  36 package org.jcp.xml.dsig.internal.dom;
  37 
  38 import java.io.ByteArrayInputStream;
  39 import java.net.URI;
  40 import java.net.URISyntaxException;
  41 import java.security.Provider;
  42 import java.util.*;
  43 
  44 import javax.xml.XMLConstants;
  45 import javax.xml.crypto.*;
  46 import javax.xml.crypto.dsig.*;
  47 import javax.xml.crypto.dom.DOMCryptoContext;
  48 import javax.xml.crypto.dom.DOMURIReference;
  49 import javax.xml.crypto.dsig.keyinfo.RetrievalMethod;
  50 import javax.xml.parsers.*;
  51 import org.w3c.dom.Attr;
  52 import org.w3c.dom.Document;
  53 import org.w3c.dom.Element;
  54 import org.w3c.dom.Node;
  55 
  56 /**
  57  * DOM-based implementation of RetrievalMethod.
  58  *
  59  * @author Sean Mullan
  60  * @author Joyce Leung
  61  */
  62 public final class DOMRetrievalMethod extends DOMStructure
  63     implements RetrievalMethod, DOMURIReference {
  64 
  65     private final List<Transform> transforms;
  66     private String uri;
  67     private String type;
  68     private Attr here;
  69 
  70     /**
  71      * Creates a <code>DOMRetrievalMethod</code> containing the specified
  72      * URIReference and List of Transforms.
  73      *
  74      * @param uri the URI
  75      * @param type the type
  76      * @param transforms a list of {@link Transform}s. The list is defensively
  77      *    copied to prevent subsequent modification. May be <code>null</code>
  78      *    or empty.
  79      * @throws IllegalArgumentException if the format of <code>uri</code> is
  80      *    invalid, as specified by Reference's URI attribute in the W3C
  81      *    specification for XML-Signature Syntax and Processing
  82      * @throws NullPointerException if <code>uriReference</code>
  83      *    is <code>null</code>
  84      * @throws ClassCastException if <code>transforms</code> contains any
  85      *    entries that are not of type {@link Transform}
  86      */
  87     public DOMRetrievalMethod(String uri, String type,
  88                               List<? extends Transform> transforms)
  89     {
  90         if (uri == null) {
  91             throw new NullPointerException("uri cannot be null");
  92         }
  93         if (transforms == null || transforms.isEmpty()) {
  94             this.transforms = Collections.emptyList();
  95         } else {
  96             this.transforms = Collections.unmodifiableList(
  97                 new ArrayList<Transform>(transforms));
  98             for (int i = 0, size = this.transforms.size(); i < size; i++) {
  99                 if (!(this.transforms.get(i) instanceof Transform)) {
 100                     throw new ClassCastException
 101                         ("transforms["+i+"] is not a valid type");
 102                 }
 103             }
 104         }
 105         this.uri = uri;
 106         if (!uri.equals("")) {
 107             try {
 108                 new URI(uri);
 109             } catch (URISyntaxException e) {
 110                 throw new IllegalArgumentException(e.getMessage());
 111             }
 112         }
 113 
 114         this.type = type;
 115     }
 116 
 117     /**
 118      * Creates a <code>DOMRetrievalMethod</code> from an element.
 119      *
 120      * @param rmElem a RetrievalMethod element
 121      */
 122     public DOMRetrievalMethod(Element rmElem, XMLCryptoContext context,
 123                               Provider provider)
 124         throws MarshalException
 125     {
 126         // get URI and Type attributes
 127         uri = DOMUtils.getAttributeValue(rmElem, "URI");
 128         type = DOMUtils.getAttributeValue(rmElem, "Type");
 129 
 130         // get here node
 131         here = rmElem.getAttributeNodeNS(null, "URI");
 132 
 133         boolean secVal = Utils.secureValidation(context);
 134 
 135         // get Transforms, if specified
 136         List<Transform> transforms = new ArrayList<Transform>();
 137         Element transformsElem = DOMUtils.getFirstChildElement(rmElem);
 138 
 139         if (transformsElem != null) {
 140             String localName = transformsElem.getLocalName();
 141             if (!localName.equals("Transforms")) {
 142                 throw new MarshalException("Invalid element name: " +
 143                                            localName + ", expected Transforms");
 144             }
 145             Element transformElem =
 146                 DOMUtils.getFirstChildElement(transformsElem, "Transform");
 147             transforms.add(new DOMTransform(transformElem, context, provider));
 148             transformElem = DOMUtils.getNextSiblingElement(transformElem);
 149             while (transformElem != null) {
 150                 String name = transformElem.getLocalName();
 151                 if (!name.equals("Transform")) {
 152                     throw new MarshalException("Invalid element name: " +
 153                                                name + ", expected Transform");
 154                 }
 155                 transforms.add
 156                     (new DOMTransform(transformElem, context, provider));
 157                 if (secVal && (transforms.size() > DOMReference.MAXIMUM_TRANSFORM_COUNT)) {
 158                     String error = "A maxiumum of " + DOMReference.MAXIMUM_TRANSFORM_COUNT + " "
 159                         + "transforms per Reference are allowed with secure validation";
 160                     throw new MarshalException(error);
 161                 }
 162                 transformElem = DOMUtils.getNextSiblingElement(transformElem);
 163             }
 164         }
 165         if (transforms.isEmpty()) {
 166             this.transforms = Collections.emptyList();
 167         } else {
 168             this.transforms = Collections.unmodifiableList(transforms);
 169         }
 170     }
 171 
 172     public String getURI() {
 173         return uri;
 174     }
 175 
 176     public String getType() {
 177         return type;
 178     }
 179 
 180     public List<Transform> getTransforms() {
 181         return transforms;
 182     }
 183 
 184     public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
 185         throws MarshalException
 186     {
 187         Document ownerDoc = DOMUtils.getOwnerDocument(parent);
 188         Element rmElem = DOMUtils.createElement(ownerDoc, "RetrievalMethod",
 189                                                 XMLSignature.XMLNS, dsPrefix);
 190 
 191         // add URI and Type attributes
 192         DOMUtils.setAttribute(rmElem, "URI", uri);
 193         DOMUtils.setAttribute(rmElem, "Type", type);
 194 
 195         // add Transforms elements
 196         if (!transforms.isEmpty()) {
 197             Element transformsElem = DOMUtils.createElement(ownerDoc,
 198                                                             "Transforms",
 199                                                             XMLSignature.XMLNS,
 200                                                             dsPrefix);
 201             rmElem.appendChild(transformsElem);
 202             for (Transform transform : transforms) {
 203                 ((DOMTransform)transform).marshal(transformsElem,
 204                                                    dsPrefix, context);
 205             }
 206         }
 207 
 208         parent.appendChild(rmElem);
 209 
 210         // save here node
 211         here = rmElem.getAttributeNodeNS(null, "URI");
 212     }
 213 
 214     public Node getHere() {
 215         return here;
 216     }
 217 
 218     public Data dereference(XMLCryptoContext context)
 219         throws URIReferenceException
 220     {
 221         if (context == null) {
 222             throw new NullPointerException("context cannot be null");
 223         }
 224 
 225         /*
 226          * If URIDereferencer is specified in context; use it, otherwise use
 227          * built-in.
 228          */
 229         URIDereferencer deref = context.getURIDereferencer();
 230         if (deref == null) {
 231             deref = DOMURIDereferencer.INSTANCE;
 232         }
 233 
 234         Data data = deref.dereference(this, context);
 235 
 236         // pass dereferenced data through Transforms
 237         try {
 238             for (Transform transform : transforms) {
 239                 data = ((DOMTransform)transform).transform(data, context);
 240             }
 241         } catch (Exception e) {
 242             throw new URIReferenceException(e);
 243         }
 244 
 245         // guard against RetrievalMethod loops
 246         if ((data instanceof NodeSetData) && Utils.secureValidation(context)) {
 247             NodeSetData nsd = (NodeSetData)data;
 248             Iterator<?> i = nsd.iterator();
 249             if (i.hasNext()) {
 250                 Node root = (Node)i.next();
 251                 if ("RetrievalMethod".equals(root.getLocalName())) {
 252                     throw new URIReferenceException(
 253                         "It is forbidden to have one RetrievalMethod point " +
 254                         "to another when secure validation is enabled");
 255                 }
 256             }
 257         }
 258 
 259         return data;
 260     }
 261 
 262     public XMLStructure dereferenceAsXMLStructure(XMLCryptoContext context)
 263         throws URIReferenceException
 264     {
 265         try {
 266             ApacheData data = (ApacheData)dereference(context);
 267             DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
 268             dbf.setNamespaceAware(true);
 269             dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
 270             DocumentBuilder db = dbf.newDocumentBuilder();
 271             Document doc = db.parse(new ByteArrayInputStream
 272                 (data.getXMLSignatureInput().getBytes()));
 273             Element kiElem = doc.getDocumentElement();
 274             if (kiElem.getLocalName().equals("X509Data")) {
 275                 return new DOMX509Data(kiElem);
 276             } else {
 277                 return null; // unsupported
 278             }
 279         } catch (Exception e) {
 280             throw new URIReferenceException(e);
 281         }
 282     }
 283 
 284     @Override
 285     public boolean equals(Object obj) {
 286         if (this == obj) {
 287             return true;
 288         }
 289         if (!(obj instanceof RetrievalMethod)) {
 290             return false;
 291         }
 292         RetrievalMethod orm = (RetrievalMethod)obj;
 293 
 294         boolean typesEqual = (type == null ? orm.getType() == null
 295                                            : type.equals(orm.getType()));
 296 
 297         return (uri.equals(orm.getURI()) &&
 298             transforms.equals(orm.getTransforms()) && typesEqual);
 299     }
 300 
 301     @Override
 302     public int hashCode() {
 303         int result = 17;
 304         if (type != null) {
 305             result = 31 * result + type.hashCode();
 306         }
 307         result = 31 * result + uri.hashCode();
 308         result = 31 * result + transforms.hashCode();
 309 
 310         return result;
 311     }
 312 }