View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.ws.security.transform;
21  
22  import org.apache.ws.security.WSConstants;
23  import org.apache.ws.security.WSDocInfo;
24  import org.apache.ws.security.message.token.PKIPathSecurity;
25  import org.apache.ws.security.message.token.SecurityTokenReference;
26  import org.apache.ws.security.message.token.X509Security;
27  import org.apache.ws.security.util.WSSecurityUtil;
28  
29  import org.apache.xml.security.c14n.Canonicalizer;
30  import org.apache.xml.security.signature.XMLSignatureInput;
31  
32  import org.w3c.dom.Document;
33  import org.w3c.dom.Element;
34  import org.w3c.dom.Node;
35  
36  import java.io.ByteArrayOutputStream;
37  import java.io.OutputStream;
38  import java.security.InvalidAlgorithmParameterException;
39  import java.security.spec.AlgorithmParameterSpec;
40  import java.util.Iterator;
41  
42  import javax.xml.crypto.Data;
43  import javax.xml.crypto.MarshalException;
44  import javax.xml.crypto.NodeSetData;
45  import javax.xml.crypto.OctetStreamData;
46  import javax.xml.crypto.XMLCryptoContext;
47  import javax.xml.crypto.XMLStructure;
48  import javax.xml.crypto.dom.DOMCryptoContext;
49  import javax.xml.crypto.dsig.TransformException;
50  import javax.xml.crypto.dsig.TransformService;
51  import javax.xml.crypto.dsig.spec.TransformParameterSpec;
52  
53  
54  /**
55   * Class STRTransform.
56   */
57  public class STRTransform extends TransformService {
58  
59      public static final String TRANSFORM_URI = 
60          "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#STR-Transform";
61      
62      public static final String TRANSFORM_WS_DOC_INFO = "transform_ws_doc_info";
63  
64      private TransformParameterSpec params;
65      
66      private Element transformElement;
67      
68      private static org.apache.commons.logging.Log log = 
69          org.apache.commons.logging.LogFactory.getLog(STRTransform.class);
70  
71      private static boolean doDebug = false;
72      
73      public final AlgorithmParameterSpec getParameterSpec() {
74          return params;
75      }
76      
77      public void init(TransformParameterSpec params)
78          throws InvalidAlgorithmParameterException {
79          this.params = params;
80      }
81      
82      public void init(XMLStructure parent, XMLCryptoContext context)
83      throws InvalidAlgorithmParameterException {
84          if (context != null && !(context instanceof DOMCryptoContext)) {
85              throw new ClassCastException
86                  ("context must be of type DOMCryptoContext");
87          }
88          if (!(parent instanceof javax.xml.crypto.dom.DOMStructure)) {
89              throw new ClassCastException("parent must be of type DOMStructure");
90          }
91          transformElement = (Element) 
92              ((javax.xml.crypto.dom.DOMStructure) parent).getNode();
93      }
94  
95      public void marshalParams(XMLStructure parent, XMLCryptoContext context)
96      throws MarshalException {
97          if (context != null && !(context instanceof DOMCryptoContext)) {
98              throw new ClassCastException
99                  ("context must be of type DOMCryptoContext");
100         }
101         if (!(parent instanceof javax.xml.crypto.dom.DOMStructure)) {
102             throw new ClassCastException("parent must be of type DOMStructure");
103         }
104         Element transformElement2 = (Element) 
105             ((javax.xml.crypto.dom.DOMStructure) parent).getNode();
106         appendChild(transformElement2, transformElement);
107         transformElement = transformElement2;
108     }
109 
110     
111     public Data transform(Data data, XMLCryptoContext xc) 
112         throws TransformException {
113         if (data == null) {
114             throw new NullPointerException("data must not be null");
115         }
116         return transformIt(data, xc, null);
117     }
118 
119     public Data transform(Data data, XMLCryptoContext xc, OutputStream os) 
120         throws TransformException {
121         if (data == null) {
122             throw new NullPointerException("data must not be null");
123         }
124         if (os == null) {
125             throw new NullPointerException("output stream must not be null");
126         }
127         return transformIt(data, xc, os);
128     }
129     
130     
131     private Data transformIt(Data data, XMLCryptoContext xc, OutputStream os) 
132         throws TransformException {
133         doDebug = log.isDebugEnabled();
134         // 
135         // First step: Get the required c14n argument and get the specified
136         // Canonicalizer
137         //
138         String canonAlgo = null;
139         Element transformParams = WSSecurityUtil.getDirectChildElement(
140             transformElement, "TransformationParameters", WSConstants.WSSE_NS
141         );
142         if (transformParams != null) {
143             Element canonElem = 
144                 WSSecurityUtil.getDirectChildElement(
145                     transformParams, "CanonicalizationMethod", WSConstants.SIG_NS
146                 );
147             canonAlgo = canonElem.getAttribute("Algorithm");
148         }
149         try {
150             //
151             // Get the input (node) to transform. 
152             //
153             Element str = null;
154             if (data instanceof NodeSetData) {
155                 NodeSetData nodeSetData = (NodeSetData)data;
156                 Iterator<?> iterator = nodeSetData.iterator();
157                 while (iterator.hasNext()) {
158                     Node node = (Node)iterator.next();
159                     if (node instanceof Element && "SecurityTokenReference".equals(node.getLocalName())) {
160                         str = (Element)node;
161                         break;
162                     }
163                 }
164             } else {
165                 try {
166                     XMLSignatureInput xmlSignatureInput = 
167                         new XMLSignatureInput(((OctetStreamData)data).getOctetStream());
168                     str = (Element)xmlSignatureInput.getSubNode();
169                 } catch (Exception ex) {
170                     throw new TransformException(ex);
171                 }
172             }
173             if (str == null) {
174                 throw new TransformException("No SecurityTokenReference found");
175             }
176             //
177             // The element to transform MUST be a SecurityTokenReference
178             // element.
179             //
180             SecurityTokenReference secRef = new SecurityTokenReference(str);
181             
182             Canonicalizer canon = Canonicalizer.getInstance(canonAlgo);
183 
184             ByteArrayOutputStream bos = null;
185             byte[] buf = null;
186             
187             //
188             // Third and fourth step are performed by dereferenceSTR()
189             //
190             Object wsDocInfoObject = xc.getProperty(TRANSFORM_WS_DOC_INFO);
191             WSDocInfo wsDocInfo = null;
192             if (wsDocInfoObject instanceof WSDocInfo) {
193                 wsDocInfo = (WSDocInfo)wsDocInfoObject;
194             }
195             if (wsDocInfo == null && doDebug) {
196                 log.debug("STRTransform: no WSDocInfo found");
197             }
198 
199             Document doc = str.getOwnerDocument();
200             Element dereferencedToken = 
201                 STRTransformUtil.dereferenceSTR(doc, secRef, wsDocInfo);
202             
203             if (dereferencedToken != null) {
204                 String type = dereferencedToken.getAttribute("ValueType");
205                 if ((X509Security.X509_V3_TYPE.equals(type) 
206                     || PKIPathSecurity.getType().equals(type))) {
207                     //
208                     // Add the WSSE/WSU namespaces to the element for C14n
209                     //
210                     WSSecurityUtil.setNamespace(
211                         dereferencedToken, WSConstants.WSSE_NS, WSConstants.WSSE_PREFIX
212                     );
213                     WSSecurityUtil.setNamespace(
214                         dereferencedToken, WSConstants.WSU_NS, WSConstants.WSU_PREFIX
215                     );
216                 }
217             }
218             
219             //
220             // C14n with specified algorithm. According to WSS Specification.
221             //
222             buf = canon.canonicalizeSubtree(dereferencedToken, "#default");
223             if (doDebug) {
224                 bos = new ByteArrayOutputStream(buf.length);
225                 bos.write(buf, 0, buf.length);
226                 log.debug("after c14n: " + bos.toString());
227             }
228 
229             //
230             // Alert: Hacks ahead According to WSS spec an Apex node must
231             // contain a default namespace. If none is availabe in the first
232             // node of the c14n output (this is the apex element) then we do
233             // some editing to insert an empty default namespace
234             // 
235             // TODO: Rework theses hacks after c14n was updated and can be
236             // instructed to insert empty default namespace if required
237             //
238             // If the problem with c14n method is solved then just do:
239             // return new XMLSignatureInput(buf);
240             
241             // start of HACK
242             StringBuilder bf = new StringBuilder(new String(buf));
243             String bf1 = bf.toString();
244 
245             //
246             // Find start and end of first element <....>, this is the Apex node
247             //
248             int gt = bf1.indexOf('>');
249             //
250             // Lookup the default namespace
251             //
252             int idx = bf1.indexOf("xmlns=");
253             //
254             // If none found or if it is outside of this (Apex) element look for
255             // first blank in, insert default namespace there (this is the
256             // correct place according to c14n specification)
257             //
258             if (idx < 0 || idx > gt) {
259                 idx = bf1.indexOf(' ');
260                 bf.insert(idx + 1, "xmlns=\"\" ");
261                 bf1 = bf.toString();
262             }
263             if (doDebug) {
264                 log.debug("last result: ");
265                 log.debug(bf1);
266             }
267             XMLSignatureInput output = new XMLSignatureInput(bf1.getBytes());
268             if (os != null) {
269                 output.updateOutputStream(os);
270                 return null;
271             }
272             return new OctetStreamData(output.getOctetStream());
273         } catch (Exception ex) {
274             throw new TransformException(ex);
275         }
276     }
277     
278     
279     public final boolean isFeatureSupported(String feature) {
280         if (feature == null) {
281             throw new NullPointerException();
282         } else {
283             return false;
284         }
285     }
286     
287     private static void appendChild(Node parent, Node child) {
288         Document ownerDoc = null;
289         if (parent.getNodeType() == Node.DOCUMENT_NODE) {
290             ownerDoc = (Document)parent;
291         } else {
292             ownerDoc = parent.getOwnerDocument();
293         }
294         if (child.getOwnerDocument() != ownerDoc) {
295             parent.appendChild(ownerDoc.importNode(child, true));
296         } else {
297             parent.appendChild(child);
298         }
299     }
300 
301 }