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.wss4j.dom.engine;
21  
22  import java.util.Collections;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  
27  import javax.security.auth.callback.CallbackHandler;
28  import javax.xml.namespace.QName;
29  
30  import org.apache.wss4j.common.bsp.BSPRule;
31  import org.apache.wss4j.common.crypto.Crypto;
32  import org.apache.wss4j.common.ext.WSSecurityException;
33  import org.apache.wss4j.dom.WSConstants;
34  import org.apache.wss4j.dom.WSDocInfo;
35  import org.apache.wss4j.dom.callback.CallbackLookup;
36  import org.apache.wss4j.dom.callback.DOMCallbackLookup;
37  import org.apache.wss4j.dom.handler.RequestData;
38  import org.apache.wss4j.dom.handler.WSHandlerResult;
39  import org.apache.wss4j.dom.processor.Processor;
40  import org.apache.wss4j.dom.saml.DOMSAMLUtil;
41  import org.apache.wss4j.dom.util.WSSecurityUtil;
42  import org.w3c.dom.Document;
43  import org.w3c.dom.Element;
44  import org.w3c.dom.Node;
45  
46  /**
47   * WS-Security Engine.
48   */
49  public class WSSecurityEngine {
50      private static final org.slf4j.Logger LOG =
51          org.slf4j.LoggerFactory.getLogger(WSSecurityEngine.class);
52  
53      /**
54       * The WSSConfig instance used by this SecurityEngine to
55       * find Processors for processing security headers
56       */
57      private WSSConfig wssConfig;
58      private boolean doDebug;
59      private CallbackLookup callbackLookup;
60  
61      /**
62       * @return      the WSSConfig object set on this instance
63       */
64      public final WSSConfig
65      getWssConfig() {
66          if (wssConfig == null) {
67              wssConfig = WSSConfig.getNewInstance();
68          }
69          return wssConfig;
70      }
71  
72      /**
73       * @param cfg   the WSSConfig instance for this WSSecurityEngine to use
74       *
75       * @return      the WSSConfig instance previously set on this
76       *              WSSecurityEngine instance
77       */
78      public final WSSConfig
79      setWssConfig(WSSConfig cfg) {
80          WSSConfig ret = wssConfig;
81          wssConfig = cfg;
82          return ret;
83      }
84  
85      /**
86       * Set the CallbackLookup object to use to locate elements
87       * @param callbackLookup the CallbackLookup object to use to locate elements
88       */
89      public void setCallbackLookup(CallbackLookup callbackLookup) {
90          this.callbackLookup = callbackLookup;
91      }
92  
93      /**
94       * Get the CallbackLookup object to use to locate elements
95       * @return the CallbackLookup object to use to locate elements
96       */
97      public CallbackLookup getCallbackLookup() {
98          return callbackLookup;
99      }
100 
101     /**
102      * Process the security header given the soap envelope as W3C document.
103      * <p/>
104      * This is the main entry point to verify or decrypt a SOAP envelope.
105      * First check if a <code>wsse:Security</code> is available with the
106      * defined actor.
107      *
108      * @param doc    the SOAP envelope as {@link Document}
109      * @param actor  the engine works on behalf of this <code>actor</code>. Refer
110      *               to the SOAP specification about <code>actor</code> or <code>role
111      *               </code>
112      * @param cb     a callback hander to the caller to resolve passwords during
113      *               encryption and UsernameToken handling
114      * @param crypto the object that implements the access to the keystore and the
115      *               handling of certificates.
116      * @return a WSHandlerResult Object containing the results of processing the security header
117      * @throws WSSecurityException
118      * @see WSSecurityEngine#processSecurityHeader(Element securityHeader, CallbackHandler cb,
119      * Crypto sigVerCrypto, Crypto decCrypto)
120      */
121     public WSHandlerResult processSecurityHeader(
122         Document doc,
123         String actor,
124         CallbackHandler cb,
125         Crypto crypto
126     ) throws WSSecurityException {
127         return processSecurityHeader(doc, actor, cb, crypto, crypto);
128     }
129 
130     /**
131      * Process the security header given the soap envelope as W3C document.
132      * <p/>
133      * This is the main entry point to verify or decrypt a SOAP envelope.
134      * First check if a <code>wsse:Security</code> is available with the
135      * defined actor.
136      *
137      * @param doc       the SOAP envelope as {@link Document}
138      * @param actor     the engine works on behalf of this <code>actor</code>. Refer
139      *                  to the SOAP specification about <code>actor</code> or <code>role
140      *                  </code>
141      * @param cb        a callback hander to the caller to resolve passwords during
142      *                  encryption and UsernameToken handling
143      * @param sigVerCrypto the object that implements the access to the keystore and the
144      *                  handling of certificates for Signature verification
145      * @param decCrypto the object that implements the access to the keystore and the
146      *                  handling of certificates for Decryption
147      * @return a WSHandlerResult Object containing the results of processing the security header
148      * @throws WSSecurityException
149      * @see WSSecurityEngine#processSecurityHeader(
150      * Element securityHeader, CallbackHandler cb, Crypto sigVerCrypto, Crypto decCrypto)
151      */
152     public WSHandlerResult processSecurityHeader(
153         Document doc,
154         String actor,
155         CallbackHandler cb,
156         Crypto sigVerCrypto,
157         Crypto decCrypto
158     ) throws WSSecurityException {
159         LOG.debug("enter processSecurityHeader()");
160 
161         if (actor == null) {
162             actor = "";
163         }
164         WSHandlerResult wsResult = null;
165         Element elem = WSSecurityUtil.getSecurityHeader(doc, actor);
166         if (elem != null) {
167             LOG.debug("Processing WS-Security header for '{}' actor.", actor);
168             wsResult = processSecurityHeader(elem, actor, cb, sigVerCrypto, decCrypto);
169         }
170         return wsResult;
171     }
172 
173     /**
174      * Process the security header given the <code>wsse:Security</code> DOM
175      * Element.
176      *
177      * This function loops over all direct child elements of the
178      * <code>wsse:Security</code> header. If it finds a known element, it
179      * transfers control to the appropriate handling function. The method
180      * processes the known child elements in the same order as they appear in
181      * the <code>wsse:Security</code> element. This is in accordance to the WS
182      * Security specification. <p/>
183      *
184      * Currently the functions can handle the following child elements:
185      *
186      * <ul>
187      * <li>{@link #SIGNATURE <code>ds:Signature</code>}</li>
188      * <li>{@link #ENCRYPTED_KEY <code>xenc:EncryptedKey</code>}</li>
189      * <li>{@link #REFERENCE_LIST <code>xenc:ReferenceList</code>}</li>
190      * <li>{@link #USERNAME_TOKEN <code>wsse:UsernameToken</code>}</li>
191      * <li>{@link #TIMESTAMP <code>wsu:Timestamp</code>}</li>
192      * </ul>
193      *
194      * Note that additional child elements can be processed if appropriate
195      * Processors have been registered with the WSSCondig instance set
196      * on this class.
197      *
198      * @param securityHeader the <code>wsse:Security</code> header element
199      * @param cb             a callback hander to the caller to resolve passwords during
200      *                       encryption and UsernameToken handling
201      * @param sigVerCrypto   the object that implements the access to the keystore and the
202      *                       handling of certificates used for Signature verification
203      * @param decCrypto      the object that implements the access to the keystore and the
204      *                       handling of certificates used for Decryption
205      * @return a WSHandlerResult Object containing the results of processing the security header
206      * @throws WSSecurityException
207      */
208     public WSHandlerResult processSecurityHeader(
209         Element securityHeader,
210         String actor,
211         CallbackHandler cb,
212         Crypto sigVerCrypto,
213         Crypto decCrypto
214     ) throws WSSecurityException {
215         RequestData data = new RequestData();
216         data.setActor(actor);
217         data.setWssConfig(getWssConfig());
218         data.setDecCrypto(decCrypto);
219         data.setSigVerCrypto(sigVerCrypto);
220         data.setCallbackHandler(cb);
221         return processSecurityHeader(securityHeader, data);
222     }
223 
224     /**
225      * Process the security header given the soap envelope as W3C document.
226      * <p/>
227      * This is the main entry point to verify or decrypt a SOAP envelope.
228      * First check if a <code>wsse:Security</code> is available with the
229      * defined actor.
230      *
231      * @param doc       the SOAP envelope as {@link Document}
232      * @param requestData    the RequestData associated with the request.  It should
233      *                       be able to provide the callback handler, cryptos, etc...
234      *                       as needed by the processing
235      * @return a WSHandlerResult Object containing the results of processing the security header
236      * @throws WSSecurityException
237      */
238     public WSHandlerResult processSecurityHeader(
239         Document doc, RequestData requestData
240     ) throws WSSecurityException {
241         if (requestData.getActor() == null) {
242             requestData.setActor("");
243         }
244         String actor = requestData.getActor();
245         WSHandlerResult wsResult = null;
246         Element elem = WSSecurityUtil.getSecurityHeader(doc, actor);
247         if (elem != null) {
248             if (doDebug) {
249                 LOG.debug("Processing WS-Security header for '" + actor + "' actor.");
250             }
251             wsResult = processSecurityHeader(elem, requestData);
252         }
253         return wsResult;
254     }
255 
256     /**
257      * Process the security header given the <code>wsse:Security</code> DOM
258      * Element.
259      *
260      * This function loops over all direct child elements of the
261      * <code>wsse:Security</code> header. If it finds a known element, it
262      * transfers control to the appropriate handling function. The method
263      * processes the known child elements in the same order as they appear in
264      * the <code>wsse:Security</code> element. This is in accordance to the WS
265      * Security specification. <p/>
266      *
267      * Currently the functions can handle the following child elements:
268      *
269      * <ul>
270      * <li>{@link #SIGNATURE <code>ds:Signature</code>}</li>
271      * <li>{@link #ENCRYPTED_KEY <code>xenc:EncryptedKey</code>}</li>
272      * <li>{@link #REFERENCE_LIST <code>xenc:ReferenceList</code>}</li>
273      * <li>{@link #USERNAME_TOKEN <code>wsse:UsernameToken</code>}</li>
274      * <li>{@link #TIMESTAMP <code>wsu:Timestamp</code>}</li>
275      * </ul>
276      *
277      * Note that additional child elements can be processed if appropriate
278      * Processors have been registered with the WSSCondig instance set
279      * on this class.
280      *
281      * @param securityHeader the <code>wsse:Security</code> header element
282      * @param requestData    the RequestData associated with the request.  It should
283      *                       be able to provide the callback handler, cryptos, etc...
284      *                       as needed by the processing
285      * @return a WSHandlerResult Object containing the results of processing the security header
286      * @throws WSSecurityException
287      */
288     public WSHandlerResult processSecurityHeader(
289         Element securityHeader,
290         RequestData requestData
291     ) throws WSSecurityException {
292         if (securityHeader == null) {
293             List<WSSecurityEngineResult> results = Collections.emptyList();
294             Map<Integer, List<WSSecurityEngineResult>> actionResults = Collections.emptyMap();
295             return new WSHandlerResult(null, results, actionResults);
296         }
297 
298         if (requestData.getWssConfig() == null) {
299             requestData.setWssConfig(getWssConfig());
300         }
301 
302         //
303         // Gather some info about the document to process and store
304         // it for retrieval. Store the implementation of signature crypto
305         // (no need for encryption --- yet)
306         //
307         WSDocInfo wsDocInfo = new WSDocInfo(securityHeader.getOwnerDocument());
308         CallbackLookup callbackLookupToUse = callbackLookup;
309         if (callbackLookupToUse == null) {
310             callbackLookupToUse = new DOMCallbackLookup(securityHeader.getOwnerDocument());
311         }
312         wsDocInfo.setCallbackLookup(callbackLookupToUse);
313         wsDocInfo.setCrypto(requestData.getSigVerCrypto());
314         wsDocInfo.setSecurityHeader(securityHeader);
315         requestData.setWsDocInfo(wsDocInfo);
316 
317         final WSSConfig cfg = getWssConfig();
318         Node node = securityHeader.getFirstChild();
319 
320         List<WSSecurityEngineResult> returnResults = new LinkedList<>();
321         boolean foundTimestamp = false;
322         while (node != null) {
323             Node nextSibling = node.getNextSibling();
324             if (Node.ELEMENT_NODE == node.getNodeType()) {
325                 QName el = new QName(node.getNamespaceURI(), node.getLocalName());
326 
327                 // Check for multiple timestamps
328                 if (foundTimestamp && el.equals(WSConstants.TIMESTAMP)) {
329                     requestData.getBSPEnforcer().handleBSPRule(BSPRule.R3227);
330                 } else if (el.equals(WSConstants.TIMESTAMP)) {
331                     foundTimestamp = true;
332                 }
333                 //
334                 // Call the processor for this token. After the processor returns,
335                 // store it for later retrieval. The token processor may store some
336                 // information about the processed token
337                 //
338                 Processor p = cfg.getProcessor(el);
339                 if (p != null) {
340                     List<WSSecurityEngineResult> results = p.handleToken((Element) node, requestData);
341                     if (!results.isEmpty()) {
342                         returnResults.addAll(0, results);
343                     }
344                 } else {
345                     if (doDebug) {
346                         LOG.debug(
347                             "Unknown Element: " + node.getLocalName() + " " + node.getNamespaceURI()
348                         );
349                     }
350                 }
351             }
352             //
353             // If the next sibling is null and the stored next sibling is not null, then we have
354             // encountered an EncryptedData element which was decrypted, and so the next sibling
355             // of the current node is null. In that case, go on to the previously stored next
356             // sibling
357             //
358             if (node.getNextSibling() == null && nextSibling != null
359                 && nextSibling.getParentNode() != null) {
360                 node = nextSibling;
361             } else {
362                 node = node.getNextSibling();
363             }
364         }
365 
366         WSHandlerResult handlerResult =
367             new WSHandlerResult(requestData.getActor(), returnResults, wsDocInfo.getActionResults());
368 
369         // Validate SAML Subject Confirmation requirements
370         if (requestData.isValidateSamlSubjectConfirmation()) {
371             Element bodyElement = callbackLookupToUse.getSOAPBody();
372             DOMSAMLUtil.validateSAMLResults(handlerResult, requestData.getTlsCerts(), bodyElement);
373         }
374 
375         wsDocInfo.clear();
376 
377         return handlerResult;
378     }
379 }