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: 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         int transformCount = 0;
 140         if (transformsElem != null) {
 141             Element transformElem =
 142                 DOMUtils.getFirstChildElement(transformsElem);
 143             while (transformElem != null) {
 144                 transforms.add
 145                     (new DOMTransform(transformElem, context, provider));
 146                 transformElem = DOMUtils.getNextSiblingElement(transformElem);
 147                 
 148                 transformCount++;
 149                 if (secVal && (transformCount > DOMReference.MAXIMUM_TRANSFORM_COUNT)) {
 150                     String error = "A maxiumum of " + DOMReference.MAXIMUM_TRANSFORM_COUNT + " " 
 151                         + "transforms per Reference are allowed with secure validation";
 152                     throw new MarshalException(error);
 153                 }
 154             }
 155         }
 156         if (transforms.isEmpty()) {
 157             this.transforms = Collections.emptyList();
 158         } else {
 159             this.transforms = Collections.unmodifiableList(transforms);
 160         }
 161     }
 162 
 163     public String getURI() {
 164         return uri;
 165     }
 166 
 167     public String getType() {
 168         return type;
 169     }
 170 
 171     public List getTransforms() {
 172         return transforms;
 173     }
 174 
 175     public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
 176         throws MarshalException
 177     {
 178         Document ownerDoc = DOMUtils.getOwnerDocument(parent);
 179         Element rmElem = DOMUtils.createElement(ownerDoc, "RetrievalMethod",
 180                                                 XMLSignature.XMLNS, dsPrefix);
 181 
 182         // add URI and Type attributes
 183         DOMUtils.setAttribute(rmElem, "URI", uri);
 184         DOMUtils.setAttribute(rmElem, "Type", type);
 185 
 186         // add Transforms elements
 187         if (!transforms.isEmpty()) {
 188             Element transformsElem = DOMUtils.createElement(ownerDoc,
 189                                                             "Transforms",
 190                                                             XMLSignature.XMLNS,
 191                                                             dsPrefix);
 192             rmElem.appendChild(transformsElem);
 193             for (Transform transform : transforms) {
 194                 ((DOMTransform)transform).marshal(transformsElem,
 195                                                    dsPrefix, context);
 196             }
 197         }
 198 
 199         parent.appendChild(rmElem);
 200 
 201         // save here node
 202         here = rmElem.getAttributeNodeNS(null, "URI");
 203     }
 204 
 205     public Node getHere() {
 206         return here;
 207     }
 208 
 209     public Data dereference(XMLCryptoContext context)
 210         throws URIReferenceException
 211     {
 212         if (context == null) {
 213             throw new NullPointerException("context cannot be null");
 214         }
 215 
 216         /*
 217          * If URIDereferencer is specified in context; use it, otherwise use 
 218          * built-in.
 219          */
 220         URIDereferencer deref = context.getURIDereferencer();
 221         if (deref == null) {
 222             deref = DOMURIDereferencer.INSTANCE;
 223         }
 224 
 225         Data data = deref.dereference(this, context);
 226 
 227         // pass dereferenced data through Transforms
 228         try {
 229             for (Transform transform : transforms) {
 230                 data = ((DOMTransform)transform).transform(data, context);
 231             }
 232         } catch (Exception e) {
 233             throw new URIReferenceException(e);
 234         }
 235 
 236         // guard against RetrievalMethod loops
 237         if ((data instanceof NodeSetData) && Utils.secureValidation(context)) {
 238             NodeSetData nsd = (NodeSetData)data;
 239             Iterator i = nsd.iterator();
 240             if (i.hasNext()) {
 241                 Node root = (Node)i.next();
 242                 if ("RetrievalMethod".equals(root.getLocalName())) {
 243                     throw new URIReferenceException(
 244                         "It is forbidden to have one RetrievalMethod point " +
 245                         "to another when secure validation is enabled");
 246                 }
 247             }
 248         }
 249 
 250         return data;
 251     }
 252 
 253     public XMLStructure dereferenceAsXMLStructure(XMLCryptoContext context)
 254         throws URIReferenceException
 255     {
 256         try {
 257             ApacheData data = (ApacheData)dereference(context);
 258             DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
 259             dbf.setNamespaceAware(true);
 260             dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
 261             DocumentBuilder db = dbf.newDocumentBuilder();
 262             Document doc = db.parse(new ByteArrayInputStream
 263                 (data.getXMLSignatureInput().getBytes()));
 264             Element kiElem = doc.getDocumentElement();
 265             if (kiElem.getLocalName().equals("X509Data")) {
 266                 return new DOMX509Data(kiElem);
 267             } else {
 268                 return null; // unsupported
 269             }
 270         } catch (Exception e) {
 271             throw new URIReferenceException(e);
 272         }
 273     }
 274 
 275     @Override
 276     public boolean equals(Object obj) {
 277         if (this == obj) {
 278             return true;
 279         }
 280         if (!(obj instanceof RetrievalMethod)) {
 281             return false;
 282         }
 283         RetrievalMethod orm = (RetrievalMethod)obj;
 284 
 285         boolean typesEqual = (type == null ? orm.getType() == null
 286                                            : type.equals(orm.getType()));
 287 
 288         return (uri.equals(orm.getURI()) &&
 289             transforms.equals(orm.getTransforms()) && typesEqual);
 290     }
 291     
 292     @Override
 293     public int hashCode() {
 294         int result = 17;
 295         if (type != null) {
 296             result = 31 * result + type.hashCode();
 297         }
 298         result = 31 * result + uri.hashCode();
 299         result = 31 * result + transforms.hashCode();
 300         
 301         return result;
 302     }
 303 }