View Javadoc

1   /*
2    * Copyright  2003-2004 The Apache Software Foundation.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *
16   */
17  
18  package org.apache.ws.security.transform;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.apache.ws.security.WSConstants;
23  import org.apache.ws.security.WSDocInfo;
24  import org.apache.ws.security.WSDocInfoStore;
25  import org.apache.ws.security.WSSConfig;
26  import org.apache.ws.security.message.token.SecurityTokenReference;
27  import org.apache.ws.security.message.token.X509Security;
28  import org.apache.ws.security.util.WSSecurityUtil;
29  import org.apache.xml.security.c14n.CanonicalizationException;
30  import org.apache.xml.security.c14n.Canonicalizer;
31  import org.apache.xml.security.c14n.InvalidCanonicalizerException;
32  import org.apache.xml.security.exceptions.XMLSecurityException;
33  import org.apache.xml.security.signature.XMLSignatureInput;
34  import org.apache.xml.security.transforms.TransformSpi;
35  import org.apache.xml.security.utils.Base64;
36  import org.apache.xml.security.utils.XMLUtils;
37  import org.w3c.dom.Document;
38  import org.w3c.dom.Element;
39  import org.w3c.dom.Node;
40  import org.w3c.dom.NodeList;
41  import org.w3c.dom.Text;
42  import org.xml.sax.SAXException;
43  
44  import javax.xml.parsers.DocumentBuilder;
45  import javax.xml.parsers.DocumentBuilderFactory;
46  import javax.xml.parsers.ParserConfigurationException;
47  import javax.xml.transform.TransformerException;
48  import java.io.ByteArrayInputStream;
49  import java.io.ByteArrayOutputStream;
50  import java.io.IOException;
51  import java.security.cert.X509Certificate;
52  
53  /***
54   * Class STRTransform
55   *
56   * @author Werner Dittmann (Werner.Dittmann@siemens.com)
57   * @version 1.0
58   */
59  public class STRTransform extends TransformSpi {
60  
61      /***
62       * Field implementedTransformURI
63       */
64      public static final String implementedTransformURI =
65              "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#STR-Transform";
66  
67      private static Log log = LogFactory.getLog(STRTransform.class.getName());
68      private static boolean doDebug = false;
69  
70      private WSDocInfo wsDocInfo = null;
71  
72      public boolean wantsOctetStream() {
73          return false;
74      }
75  
76      public boolean wantsNodeSet() {
77          return true;
78      }
79  
80      public boolean returnsOctetStream() {
81          return true;
82      }
83  
84      public boolean returnsNodeSet() {
85          return false;
86      }
87  
88      /***
89       * Method engineGetURI
90       */
91      protected String engineGetURI() {
92          return STRTransform.implementedTransformURI;
93      }
94  
95      /***
96       * Method enginePerformTransform
97       *
98       * @param input
99       * @throws CanonicalizationException
100      * @throws InvalidCanonicalizerException
101      */
102     protected XMLSignatureInput enginePerformTransform(XMLSignatureInput input)
103             throws IOException,
104                    CanonicalizationException,
105                    InvalidCanonicalizerException {
106 
107         doDebug = log.isDebugEnabled();
108 
109         if (doDebug) {
110             log.debug("Beginning STRTransform..." + input.toString());
111         }
112 
113         try {
114 
115             /*
116              * Get the main document, that is the complete SOAP
117              * request document
118              */
119             Document thisDoc = this._transformObject.getDocument();
120             int docHash = thisDoc.hashCode();
121             if (doDebug) {
122                 log.debug("doc: " + thisDoc.toString() + ", " + docHash);
123             }
124 
125             /*
126              * Here we get some information about the document that is being
127              * processed, in partucular the crypto implementation, and already
128              * detected BST that may be used later during dereferencing.
129              */
130             wsDocInfo = WSDocInfoStore.lookup(docHash);
131             if (wsDocInfo == null) {
132                 throw (new CanonicalizationException("no WSDocInfo found"));
133             }
134 
135             /*
136              * According to the OASIS WS Specification
137              * "Web Services Security: SOAP Message Security 1.0"
138              * Monday, 19 January 2004, chapter 8.3 describes that
139              * the input node set must be processed bythe c14n that
140              * is specified in the argument element of the STRTransform
141              * element.
142              *
143              * First step: Get the required c14n argument. After that, get
144              * the c14n, feed the node set into c14n and get back the byte[].
145              * The byte[] contains the XML doc part to be
146              * signed or verified. Then reparse the byte[] to get the DOM.
147              */
148 
149             String canonAlgo = null;
150             if (this._transformObject.length(WSConstants.WSSE_NS,
151                     "TransformationParameters") == 1) {
152                 Element tmpE = XMLUtils.selectNode(
153                         this._transformObject.getElement().getFirstChild(),
154                         WSConstants.WSSE_NS, "TransformationParameters", 0);
155                 Element canonElem = (Element) WSSecurityUtil.getDirectChild(
156                     tmpE, "CanonicalizationMethod", WSConstants.SIG_NS);
157                 canonAlgo = canonElem.getAttribute("Algorithm");
158                 if (doDebug) {
159                     log.debug("CanonAlgo: " + canonAlgo);
160                 }
161             }
162             Canonicalizer canon = Canonicalizer.getInstance(canonAlgo);
163             byte buf[] = canon.canonicalizeXPathNodeSet(input.getNodeSet());
164 
165             ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.length);
166             bos.write(buf, 0, buf.length);
167 
168             if (doDebug) {
169                 log.debug("canon bos: " + bos.toString());
170             }
171 
172             DocumentBuilderFactory dfactory = DocumentBuilderFactory
173                     .newInstance();
174             dfactory.setValidating(false);
175             dfactory.setNamespaceAware(true);
176 
177             DocumentBuilder db = dfactory.newDocumentBuilder();
178 
179             Document doc = db
180                     .parse(new ByteArrayInputStream(bos.toByteArray()));
181 
182             /*
183              * Second step: find the STR element inside the resulting XML doc,
184              * check if STR contains some reference to an security token.
185              */
186 
187             NodeList nodeList =
188                     doc.getElementsByTagNameNS(WSConstants.WSSE_NS,
189                             "SecurityTokenReference");
190 
191             Element str = null;
192             Element tmpEl = (Element) nodeList.item(0);
193             if (doDebug) {
194                 log.debug("STR: " + tmpEl.toString());
195             }
196             /*
197              * Third and forth step are performed by derefenceSTR()
198              */
199             SecurityTokenReference secRef = new SecurityTokenReference(
200                 WSSConfig.getDefaultWSConfig(), tmpEl);
201 
202             str = dereferenceSTR(thisDoc, secRef);
203             /*
204              * Keep in mind: the returned element belong to "thisDoc", thus
205              * import it to "doc" before replacing it.
206              *
207              * Fifth step: replace the STR with the above created/copied BST, feed
208              * this result in the specified c14n method and return this to
209              * the caller.
210              *
211              */
212             str = (Element) doc.importNode(str, true);
213 
214             Node parent = tmpEl.getParentNode(); // point to document node
215 //            parent.replaceChild(str, tmpEl); // replace STR with new node
216 //
217 
218             /*
219              * Alert: Hacks ahead
220              *
221              * TODO: Rework theses hacks after c14n was updated.
222              */
223 
224             /*
225              * HACK 1:
226              * Create a top level fake element with a defined default namespace
227              * setting (urn:X). Replace the STR node with this fake element that
228              * is now the top level element. Append our result element to the
229              * fake element, then call c14n. This forces the c14n to insert
230              * xmlns="" if necessary. However, before we handover the result we
231              * have to remove the fake element. See string buffer operation
232              * below.
233              */
234             Element tmpEl1 = doc.createElement("temp");
235             tmpEl1.setAttributeNS(WSConstants.XMLNS_NS, "xmlns", "urn:X");
236             parent.replaceChild(tmpEl1, tmpEl); // replace STR with new node
237 
238             tmpEl1.appendChild(str);
239             // End of HACK 1
240 
241             // XMLUtils.circumventBug2650(doc); // No longer needed???
242 
243             /*
244              * C14n with specified algorithm. According to WSS Specification.
245              */
246             buf = canon.canonicalizeSubtree(doc, "#default");
247 
248             // If the problem with c14n method is solved then just do:
249 
250             /* return new XMLSignatureInput(buf); */
251 
252             /*
253              * HACK 2
254              */
255             bos = new ByteArrayOutputStream(buf.length);
256             bos.write(buf, 0, buf.length);
257 
258             if (doDebug) {
259                 log.debug("after c14n: " + bos.toString());
260             }
261 
262             /*
263              * Here we delete the previously inserted fake element from the
264              * serialized XML.
265              */
266             StringBuffer bf = new StringBuffer(bos.toString());
267             String bf1 = bf.substring("<temp xmlns=\"urn:X\">".length(), bf.length() - "</temp>".length());
268 
269             if (doDebug) {
270                 log.debug("last result: ");
271                 log.debug(bf1.toString());
272             }
273             return new XMLSignatureInput(bf1.getBytes());
274             // End of HACK 2
275 
276         } catch (IOException ex) {
277             throw new CanonicalizationException("empty", ex);
278         } catch (ParserConfigurationException ex) {
279             throw new CanonicalizationException("empty", ex);
280         } catch (XMLSecurityException ex) {
281             throw new CanonicalizationException("empty", ex);
282         } catch (SAXException ex) {
283             throw new CanonicalizationException("empty", ex);
284         } catch (TransformerException ex) {
285             throw new CanonicalizationException("empty", ex);
286         } catch (Exception ex) {
287             throw new CanonicalizationException("empty", ex);
288         }
289     }
290 
291     private Element dereferenceSTR(Document doc, SecurityTokenReference secRef)
292             throws Exception {
293 
294         /*
295          * Third step: locate the security token referenced by the STR
296          * element. Either the Token is contained in the document as a
297          * BinarySecurityToken or stored in some key storage.
298          *
299          * Forth step: after security token was located, prepare it. If its
300          * reference via a direct reference, i.e. a relative URI that references
301          * the BST directly in the message then just return that element.
302          * Otherwise wrap the located token in a newly created BST element
303          * as described in WSS Specification.
304          *
305          * Note: every element (also newly created elements) belong to the
306          * document defined by the doc parameter. This is the main SOAP document
307          * (thisDoc) and _not_ the document part that is to be signed/verified. Thus
308          * the caller must import the returned element into the document
309          * part that is signed/verified.
310          *
311          */
312         Element tokElement = null;
313 
314         /*
315          * First case: direct reference, according to chap 7.2 of OASIS
316          * WS specification (main document). Only in this case return
317          * a true reference to the BST. Copying is done by the caller.
318          */
319         if (secRef.containsReference()) {
320             if (doDebug) {
321                 log.debug("STR: Reference");
322             }
323             tokElement = secRef.getTokenElement(doc, wsDocInfo);
324             if (tokElement == null) {
325                 throw new CanonicalizationException("empty");
326             }
327         }
328         /*
329          * second case: IssuerSerial, first try to get embedded
330          * certificate, if that fails, lookup in keystore, wrap
331          * in BST according to specification
332          */
333         else if (secRef.containsX509IssuerSerial()) {
334             if (doDebug) {
335                 log.debug("STR: IssuerSerial");
336             }
337             X509Certificate cert = null;
338             X509Security x509token = null;
339             // Disable check for embedded, always get from store (comment from Merlin,
340             // Betrust)
341             // x509token = secRef.getEmbeddedTokenFromIS(doc, wsDocInfo.getCrypto());
342             if (x509token != null) {
343                 cert = x509token.getX509Certificate(wsDocInfo.getCrypto());
344             } else {
345                 X509Certificate[] certs = secRef.getX509IssuerSerial(wsDocInfo.getCrypto());
346                 if (certs == null || certs.length == 0 || certs[0] == null) {
347                     throw new CanonicalizationException("empty");
348                 }
349                 cert = certs[0];
350             }
351             tokElement = createBST(doc, cert, secRef.getElement());
352         }
353         /*
354          * third case: KeyIdentifier, must be SKI, first try to get embedded
355          * certificate, if that fails, lookup in keystore, wrap
356          * in BST according to specification. No other KeyIdentifier
357          * type handled here - just SKI
358          */
359         else if (secRef.containsKeyIdentifier()) {
360             if (doDebug) {
361                 log.debug("STR: KeyIdentifier");
362             }
363             X509Certificate cert = null;
364             X509Security x509token = null;
365             // Disable check for embedded, always get from store (comment from Merlin,
366             // Betrust)
367             // x509token = secRef.getEmbeddedTokenFromSKI(doc, wsDocInfo.getCrypto());
368             if (x509token != null) {
369                 cert = x509token.getX509Certificate(wsDocInfo.getCrypto());
370             } else {
371                 X509Certificate[] certs = secRef.getKeyIdentifier(wsDocInfo.getCrypto());
372                 if (certs == null || certs.length == 0 || certs[0] == null) {
373                     throw new CanonicalizationException("empty");
374                 }
375                 cert = certs[0];
376             }
377             tokElement = createBST(doc, cert, secRef.getElement());
378         }
379         return (Element) tokElement;
380     }
381 
382     private Element createBST(Document doc,
383                               X509Certificate cert,
384                               Element secRefE)
385             throws Exception {
386         byte data[] = cert.getEncoded();
387         String prefix = WSSecurityUtil.getPrefixNS(WSConstants.WSSE_NS, secRefE);
388         Element elem =
389                 doc.createElementNS(WSConstants.WSSE_NS,
390                         prefix + ":BinarySecurityToken");
391         WSSecurityUtil.setNamespace(elem, WSConstants.WSSE_NS, prefix);
392         elem.setAttributeNS(WSConstants.XMLNS_NS, "xmlns", "");
393         elem.setAttributeNS(null, "ValueType", X509Security.getType(WSSConfig.getDefaultWSConfig()));
394         Text certText = doc.createTextNode(Base64.encode(data, 0));  // no line wrap
395         elem.appendChild(certText);
396         return elem;
397     }
398 }