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.processor;
21  
22  import java.security.Principal;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import javax.crypto.SecretKey;
29  
30  import org.apache.ws.security.WSConstants;
31  import org.apache.ws.security.WSDataRef;
32  import org.apache.ws.security.WSDerivedKeyTokenPrincipal;
33  import org.apache.ws.security.WSDocInfo;
34  import org.apache.ws.security.WSSecurityEngineResult;
35  import org.apache.ws.security.WSSecurityException;
36  import org.apache.ws.security.components.crypto.AlgorithmSuite;
37  import org.apache.ws.security.components.crypto.AlgorithmSuiteValidator;
38  import org.apache.ws.security.handler.RequestData;
39  import org.apache.ws.security.message.CallbackLookup;
40  import org.apache.ws.security.message.DOMCallbackLookup;
41  import org.apache.ws.security.message.token.SecurityTokenReference;
42  import org.apache.ws.security.str.STRParser;
43  import org.apache.ws.security.str.SecurityTokenRefSTRParser;
44  import org.apache.ws.security.util.WSSecurityUtil;
45  import org.apache.xml.security.encryption.XMLCipher;
46  import org.apache.xml.security.encryption.XMLEncryptionException;
47  import org.w3c.dom.Attr;
48  import org.w3c.dom.Document;
49  import org.w3c.dom.Element;
50  import org.w3c.dom.Node;
51  
52  public class ReferenceListProcessor implements Processor {
53      private static org.apache.commons.logging.Log log = 
54          org.apache.commons.logging.LogFactory.getLog(ReferenceListProcessor.class);
55      
56      public List<WSSecurityEngineResult> handleToken(
57          Element elem, 
58          RequestData data, 
59          WSDocInfo wsDocInfo 
60      ) throws WSSecurityException {
61          if (log.isDebugEnabled()) {
62              log.debug("Found reference list element");
63          }
64          List<WSDataRef> dataRefs = handleReferenceList(elem, data, wsDocInfo);
65          WSSecurityEngineResult result = 
66              new WSSecurityEngineResult(WSConstants.ENCR, dataRefs);
67          result.put(WSSecurityEngineResult.TAG_ID, elem.getAttributeNS(null, "Id"));
68          wsDocInfo.addTokenElement(elem);
69          wsDocInfo.addResult(result);
70          return java.util.Collections.singletonList(result);
71      }
72  
73      /**
74       * Dereferences and decodes encrypted data elements.
75       * 
76       * @param elem contains the <code>ReferenceList</code> to the encrypted
77       *             data elements
78       * @param cb the callback handler to get the key for a key name stored if
79       *           <code>KeyInfo</code> inside the encrypted data elements
80       */
81      private List<WSDataRef> handleReferenceList(
82          Element elem, 
83          RequestData data,
84          WSDocInfo wsDocInfo
85      ) throws WSSecurityException {
86          List<WSDataRef> dataRefs = new ArrayList<WSDataRef>();
87          //find out if there's an EncryptedKey in the doc (AsymmetricBinding)
88          Element wsseHeaderElement = wsDocInfo.getSecurityHeader();
89          boolean asymBinding = WSSecurityUtil.getDirectChildElement(
90              wsseHeaderElement, WSConstants.ENC_KEY_LN, WSConstants.ENC_NS) != null;
91          for (Node node = elem.getFirstChild(); 
92              node != null; 
93              node = node.getNextSibling()
94          ) {
95              if (Node.ELEMENT_NODE == node.getNodeType()
96                  && WSConstants.ENC_NS.equals(node.getNamespaceURI())
97                  && "DataReference".equals(node.getLocalName())) {
98                  String dataRefURI = ((Element) node).getAttribute("URI");
99                  if (dataRefURI.charAt(0) == '#') {
100                     dataRefURI = dataRefURI.substring(1);
101                 }
102                 
103                 if (wsDocInfo.getResultByTag(WSConstants.ENCR, dataRefURI) == null) {
104                     WSDataRef dataRef = 
105                         decryptDataRefEmbedded(
106                             elem.getOwnerDocument(), dataRefURI, data, wsDocInfo, asymBinding);
107                     dataRefs.add(dataRef);
108                 }
109             }
110         }
111         
112         return dataRefs;
113     }
114 
115     
116     /**
117      * Decrypt an (embedded) EncryptedData element referenced by dataRefURI.
118      */
119     private WSDataRef decryptDataRefEmbedded(
120         Document doc, 
121         String dataRefURI, 
122         RequestData data,
123         WSDocInfo wsDocInfo,
124         boolean asymBinding
125     ) throws WSSecurityException {
126         if (log.isDebugEnabled()) {
127             log.debug("Found data reference: " + dataRefURI);
128         }
129         //
130         // Find the encrypted data element referenced by dataRefURI
131         //
132         Element encryptedDataElement = findEncryptedDataElement(doc, wsDocInfo, dataRefURI);
133         
134         if (encryptedDataElement != null && asymBinding && data.isRequireSignedEncryptedDataElements()) {
135             WSSecurityUtil.verifySignedElement(encryptedDataElement, doc, wsDocInfo.getSecurityHeader());
136         }
137         //
138         // Prepare the SecretKey object to decrypt EncryptedData
139         //
140         String symEncAlgo = X509Util.getEncAlgo(encryptedDataElement);
141         Element keyInfoElement = 
142             (Element)WSSecurityUtil.getDirectChildElement(
143                 encryptedDataElement, "KeyInfo", WSConstants.SIG_NS
144             );
145         // KeyInfo cannot be null
146         if (keyInfoElement == null) {
147             throw new WSSecurityException(WSSecurityException.INVALID_SECURITY, "noKeyinfo");
148         }
149         // Check BSP compliance
150         if (data.getWssConfig().isWsiBSPCompliant()) {
151             checkBSPCompliance(keyInfoElement, symEncAlgo);
152         }
153         //
154         // Try to get a security reference token, if none found try to get a
155         // shared key using a KeyName.
156         //
157         Element secRefToken = 
158             WSSecurityUtil.getDirectChildElement(
159                 keyInfoElement, "SecurityTokenReference", WSConstants.WSSE_NS
160             );
161         SecretKey symmetricKey = null;
162         Principal principal = null;
163         if (secRefToken == null) {
164             symmetricKey = X509Util.getSharedKey(keyInfoElement, symEncAlgo, data.getCallbackHandler());
165         } else {
166             STRParser strParser = new SecurityTokenRefSTRParser();
167             Map<String, Object> parameters = new HashMap<String, Object>();
168             parameters.put(SecurityTokenRefSTRParser.SIGNATURE_METHOD, symEncAlgo);
169             strParser.parseSecurityTokenReference(
170                 secRefToken, data,
171                 wsDocInfo, parameters
172             );
173             byte[] secretKey = strParser.getSecretKey();
174             principal = strParser.getPrincipal();
175             symmetricKey = WSSecurityUtil.prepareSecretKey(symEncAlgo, secretKey);
176         }
177         
178         // Check for compliance against the defined AlgorithmSuite
179         AlgorithmSuite algorithmSuite = data.getAlgorithmSuite();
180         if (algorithmSuite != null) {
181             AlgorithmSuiteValidator algorithmSuiteValidator = new
182                 AlgorithmSuiteValidator(algorithmSuite);
183 
184             if (principal instanceof WSDerivedKeyTokenPrincipal) {
185                 algorithmSuiteValidator.checkDerivedKeyAlgorithm(
186                     ((WSDerivedKeyTokenPrincipal)principal).getAlgorithm()
187                 );
188                 algorithmSuiteValidator.checkEncryptionDerivedKeyLength(
189                     ((WSDerivedKeyTokenPrincipal)principal).getLength()
190                 );
191             }
192 
193             algorithmSuiteValidator.checkSymmetricKeyLength(symmetricKey.getEncoded().length);
194             algorithmSuiteValidator.checkSymmetricEncryptionAlgorithm(symEncAlgo);
195         }
196 
197         return 
198             decryptEncryptedData(
199                 doc, dataRefURI, encryptedDataElement, symmetricKey, symEncAlgo
200             );
201     }
202     
203     /**
204      * Check for BSP compliance
205      * @param keyInfoElement The KeyInfo element child
206      * @param encAlgo The encryption algorithm
207      * @throws WSSecurityException
208      */
209     private static void checkBSPCompliance(
210         Element keyInfoElement, 
211         String encAlgo
212     ) throws WSSecurityException {
213         // We can only have one token reference
214         int result = 0;
215         Node node = keyInfoElement.getFirstChild();
216         Element child = null;
217         while (node != null) {
218             if (Node.ELEMENT_NODE == node.getNodeType()) {
219                 result++;
220                 child = (Element)node;
221             }
222             node = node.getNextSibling();
223         }
224         if (result != 1) {
225             throw new WSSecurityException(
226                 WSSecurityException.INVALID_SECURITY, "invalidDataRef"
227             );
228         }
229         
230         if (!WSConstants.WSSE_NS.equals(child.getNamespaceURI()) || 
231             !SecurityTokenReference.SECURITY_TOKEN_REFERENCE.equals(child.getLocalName())) {
232             throw new WSSecurityException(
233                 WSSecurityException.INVALID_SECURITY, "noSecTokRef"
234             );
235         }
236         
237         // EncryptionAlgorithm cannot be null
238         if (encAlgo == null) {
239             throw new WSSecurityException(
240                 WSSecurityException.UNSUPPORTED_ALGORITHM, "noEncAlgo"
241             );
242         }
243         // EncryptionAlgorithm must be 3DES, or AES128, or AES256
244         if (!WSConstants.TRIPLE_DES.equals(encAlgo)
245             && !WSConstants.AES_128.equals(encAlgo)
246             && !WSConstants.AES_128_GCM.equals(encAlgo)
247             && !WSConstants.AES_256.equals(encAlgo)
248             && !WSConstants.AES_256_GCM.equals(encAlgo)) {
249             throw new WSSecurityException(
250                 WSSecurityException.INVALID_SECURITY, "badEncAlgo", new Object[]{encAlgo}
251             );
252         }
253     }
254 
255     /**
256      * Look up the encrypted data. First try Id="someURI". If no such Id then try 
257      * wsu:Id="someURI".
258      * 
259      * @param doc The document in which to find EncryptedData
260      * @param wsDocInfo The WSDocInfo object to use
261      * @param dataRefURI The URI of EncryptedData
262      * @return The EncryptedData element
263      * @throws WSSecurityException if the EncryptedData element referenced by dataRefURI is 
264      * not found
265      */
266     public static Element
267     findEncryptedDataElement(
268         Document doc,
269         WSDocInfo wsDocInfo,
270         String dataRefURI
271     ) throws WSSecurityException {
272         CallbackLookup callbackLookup = wsDocInfo.getCallbackLookup();
273         if (callbackLookup == null) {
274             callbackLookup = new DOMCallbackLookup(doc);
275         }
276         Element encryptedDataElement = 
277             callbackLookup.getElement(dataRefURI, null, true);
278         if (encryptedDataElement == null) {
279             throw new WSSecurityException(
280                 WSSecurityException.INVALID_SECURITY, "dataRef", new Object[] {dataRefURI}
281             );
282         }
283         if (encryptedDataElement.getLocalName().equals(WSConstants.ENCRYPTED_HEADER)
284             && encryptedDataElement.getNamespaceURI().equals(WSConstants.WSSE11_NS)) {
285             Node child = encryptedDataElement.getFirstChild();
286             while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
287                 child = child.getNextSibling();
288             }
289             return (Element)child;
290         }
291         return encryptedDataElement;
292     }
293 
294     
295     /**
296      * Decrypt the EncryptedData argument using a SecretKey.
297      * @param doc The (document) owner of EncryptedData
298      * @param dataRefURI The URI of EncryptedData
299      * @param encData The EncryptedData element
300      * @param symmetricKey The SecretKey with which to decrypt EncryptedData
301      * @param symEncAlgo The symmetric encryption algorithm to use
302      * @throws WSSecurityException
303      */
304     public static WSDataRef
305     decryptEncryptedData(
306         Document doc,
307         String dataRefURI,
308         Element encData,
309         SecretKey symmetricKey,
310         String symEncAlgo
311     ) throws WSSecurityException {
312         XMLCipher xmlCipher = null;
313         try {
314             xmlCipher = XMLCipher.getInstance(symEncAlgo);
315             xmlCipher.setSecureValidation(true);
316             xmlCipher.init(XMLCipher.DECRYPT_MODE, symmetricKey);
317         } catch (XMLEncryptionException ex) {
318             throw new WSSecurityException(
319                 WSSecurityException.UNSUPPORTED_ALGORITHM, null, null, ex
320             );
321         }
322 
323         WSDataRef dataRef = new WSDataRef();
324         dataRef.setWsuId(dataRefURI);
325         dataRef.setAlgorithm(symEncAlgo);
326         boolean content = X509Util.isContent(encData);
327         dataRef.setContent(content);
328         
329         Node parent = encData.getParentNode();
330         Node previousSibling = encData.getPreviousSibling();
331         if (content) {
332             encData = (Element) encData.getParentNode();
333             parent = encData.getParentNode();
334         }
335         
336         try {
337             xmlCipher.doFinal(doc, encData, content);
338         } catch (Exception ex) {
339             throw new WSSecurityException(WSSecurityException.FAILED_CHECK, null, null, ex);
340         }
341         
342         if (parent.getLocalName().equals(WSConstants.ENCRYPTED_HEADER)
343             && parent.getNamespaceURI().equals(WSConstants.WSSE11_NS)) {
344                 
345             Node decryptedHeader = parent.getFirstChild();
346             Node soapHeader = parent.getParentNode();
347             soapHeader.replaceChild(decryptedHeader, parent);
348 
349             dataRef.setProtectedElement((Element)decryptedHeader);
350             dataRef.setXpath(getXPath(decryptedHeader));
351         } else if (content) {
352             dataRef.setProtectedElement(encData);
353             dataRef.setXpath(getXPath(encData));
354         } else {
355             Node decryptedNode;
356             if (previousSibling == null) {
357                 decryptedNode = parent.getFirstChild();
358             } else {
359                 decryptedNode = previousSibling.getNextSibling();
360             }
361             if (decryptedNode != null && Node.ELEMENT_NODE == decryptedNode.getNodeType()) {
362                 dataRef.setProtectedElement((Element)decryptedNode);
363             }
364             dataRef.setXpath(getXPath(decryptedNode));
365         }
366         
367         return dataRef;
368     }
369     
370     
371     public String getId() {
372         return null;
373     }
374 
375     
376     /**
377      * @param decryptedNode the decrypted node
378      * @return a fully built xpath 
379      *        (eg. &quot;/soapenv:Envelope/soapenv:Body/ns:decryptedElement&quot;)
380      *        if the decryptedNode is an Element or an Attr node and is not detached
381      *        from the document. <code>null</code> otherwise
382      */
383     public static String getXPath(Node decryptedNode) {
384         if (decryptedNode == null) {
385             return null;
386         }
387         
388         String result = "";
389         if (Node.ELEMENT_NODE == decryptedNode.getNodeType()) {
390             result = decryptedNode.getNodeName();
391             result = prependFullPath(result, decryptedNode.getParentNode());
392         } else if (Node.ATTRIBUTE_NODE == decryptedNode.getNodeType()) {
393             result = "@" + decryptedNode.getNodeName();
394             result = prependFullPath(result, ((Attr)decryptedNode).getOwnerElement());
395         } else {
396             return null;
397         }
398         
399         return result;
400     }
401 
402 
403     /**
404      * Recursively build an absolute xpath (starting with the root &quot;/&quot;)
405      * 
406      * @param xpath the xpath expression built so far
407      * @param node the current node whose name is to be prepended
408      * @return a fully built xpath
409      */
410     private static String prependFullPath(String xpath, Node node) {
411         if (node == null) {
412             // probably a detached node... not really useful
413             return null;
414         } else if (Node.ELEMENT_NODE == node.getNodeType()) {
415             xpath = node.getNodeName() + "/" + xpath;
416             return prependFullPath(xpath, node.getParentNode());
417         } else if (Node.DOCUMENT_NODE == node.getNodeType()) {
418             return "/" + xpath;
419         } else {
420             return prependFullPath(xpath, node.getParentNode());
421         }
422     }
423 
424 }