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  package org.apache.wss4j.stax.impl.processor.input;
20  
21  import java.lang.reflect.InvocationTargetException;
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  
25  import javax.xml.namespace.QName;
26  import javax.xml.stream.XMLStreamConstants;
27  import javax.xml.stream.XMLStreamException;
28  
29  import org.apache.wss4j.common.bsp.BSPRule;
30  import org.apache.wss4j.common.ext.WSSecurityException;
31  import org.apache.wss4j.stax.ext.WSInboundSecurityContext;
32  import org.apache.wss4j.stax.ext.WSSConstants;
33  import org.apache.wss4j.stax.ext.WSSSecurityProperties;
34  import org.apache.wss4j.stax.securityEvent.NoSecuritySecurityEvent;
35  import org.apache.wss4j.stax.utils.WSSUtils;
36  import org.apache.xml.security.exceptions.XMLSecurityException;
37  import org.apache.xml.security.stax.config.SecurityHeaderHandlerMapper;
38  import org.apache.xml.security.stax.ext.AbstractInputProcessor;
39  import org.apache.xml.security.stax.ext.InputProcessorChain;
40  import org.apache.xml.security.stax.ext.XMLSecurityHeaderHandler;
41  import org.apache.xml.security.stax.ext.XMLSecurityProperties;
42  import org.apache.xml.security.stax.ext.stax.XMLSecEndElement;
43  import org.apache.xml.security.stax.ext.stax.XMLSecEvent;
44  import org.apache.xml.security.stax.ext.stax.XMLSecStartElement;
45  import org.apache.xml.security.stax.impl.processor.input.XMLEventReaderInputProcessor;
46  import org.apache.xml.security.stax.impl.util.IDGenerator;
47  
48  /**
49   * Processor for the Security-Header XML Structure.
50   * This processor instantiates more processors on demand
51   */
52  public class SecurityHeaderInputProcessor extends AbstractInputProcessor {
53  
54      protected static final org.slf4j.Logger LOG =
55          org.slf4j.LoggerFactory.getLogger(SecurityHeaderInputProcessor.class);
56  
57      private final ArrayDeque<XMLSecEvent> xmlSecEventList = new ArrayDeque<>();
58      private int startIndexForProcessor;
59  
60      public SecurityHeaderInputProcessor(WSSSecurityProperties securityProperties) {
61          super(securityProperties);
62          setPhase(WSSConstants.Phase.POSTPROCESSING);
63      }
64  
65      @Override
66      public XMLSecEvent processHeaderEvent(InputProcessorChain inputProcessorChain)
67              throws XMLStreamException, XMLSecurityException {
68          return null;
69      }
70  
71      @Override
72      public XMLSecEvent processEvent(InputProcessorChain inputProcessorChain)
73              throws XMLStreamException, XMLSecurityException {
74  
75          //buffer all events until the end of the security header
76          final InputProcessorChain subInputProcessorChain = inputProcessorChain.createSubChain(this);
77          final InternalSecurityHeaderBufferProcessor internalSecurityHeaderBufferProcessor
78                  = new InternalSecurityHeaderBufferProcessor(getSecurityProperties());
79          subInputProcessorChain.addProcessor(internalSecurityHeaderBufferProcessor);
80  
81          boolean responsibleSecurityHeaderFound = false;
82          boolean timestampFound = false;
83  
84          XMLSecEvent xmlSecEvent;
85          do {
86              subInputProcessorChain.reset();
87              xmlSecEvent = subInputProcessorChain.processHeaderEvent();
88  
89              switch (xmlSecEvent.getEventType()) {   //NOPMD
90                  case XMLStreamConstants.START_ELEMENT:
91                      XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement();
92                      int documentLevel = xmlSecStartElement.getDocumentLevel();
93  
94                      if (documentLevel == 1) {
95                          if (WSSUtils.getSOAPMessageVersionNamespace(xmlSecStartElement) == null) {
96                              throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, "notASOAPMessage");
97                          }
98                      } else if (documentLevel == 3
99                              && xmlSecStartElement.getName().equals(WSSConstants.TAG_WSSE_SECURITY)
100                             && WSSUtils.isInSOAPHeader(xmlSecStartElement)) {
101 
102                         if (!WSSUtils.isResponsibleActorOrRole(xmlSecStartElement,
103                                 ((WSSSecurityProperties) getSecurityProperties()).getActor())) {
104                             continue;
105                         }
106                         responsibleSecurityHeaderFound = true;
107 
108                     } else if (documentLevel == 4 && responsibleSecurityHeaderFound
109                             && WSSUtils.isInSecurityHeader(xmlSecStartElement,
110                             ((WSSSecurityProperties) getSecurityProperties()).getActor())) {
111                         startIndexForProcessor = xmlSecEventList.size() - 1;
112 
113                         //special handling for EncryptedData in the SecurityHeader. This way, if for example
114                         // a token was encrypted we have the possibility to decrypt it before so that we
115                         // are able to engage the appropriate processor for the token.
116                         if (WSSConstants.TAG_xenc_EncryptedData.equals(xmlSecStartElement.getName())) {
117                             engageSecurityHeaderHandler(subInputProcessorChain, getSecurityProperties(),
118                                     xmlSecEventList, startIndexForProcessor, xmlSecStartElement.getName());
119                         }
120                     } else if (documentLevel == 5 && responsibleSecurityHeaderFound
121                             && WSSUtils.isInSecurityHeader(xmlSecStartElement,
122                             ((WSSSecurityProperties) getSecurityProperties()).getActor())
123                             && WSSConstants.TAG_xenc_EncryptedData.equals(xmlSecStartElement.getName())) {
124                         startIndexForProcessor = xmlSecEventList.size() - 1;
125 
126                         // Same goes as per EncryptedData above. This is when a child of a security header
127                         // element is encrypted (e.g. EncryptedAssertion)
128                         engageSecurityHeaderHandler(subInputProcessorChain, getSecurityProperties(),
129                                 xmlSecEventList, startIndexForProcessor, xmlSecStartElement.getName());
130                     }
131                     break;
132                 case XMLStreamConstants.END_ELEMENT:
133                     XMLSecEndElement xmlSecEndElement = xmlSecEvent.asEndElement();
134                     documentLevel = xmlSecEndElement.getDocumentLevel();
135                     if (documentLevel == 3 && responsibleSecurityHeaderFound
136                             && xmlSecEndElement.getName().equals(WSSConstants.TAG_WSSE_SECURITY)) {
137 
138                         return finalizeHeaderProcessing(
139                                 inputProcessorChain, subInputProcessorChain,
140                                 internalSecurityHeaderBufferProcessor, xmlSecEventList);
141 
142                     } else if (documentLevel == 4 && responsibleSecurityHeaderFound
143                             && WSSUtils.isInSecurityHeader(xmlSecEndElement,
144                             ((WSSSecurityProperties) getSecurityProperties()).getActor())) {
145                         //we are in the security header and the depth is +1, so every child
146                         //element should have a responsible handler with the exception of an EncryptedData SecurityHeader
147                         //which is already handled in the above StartElement Logic (@see comment above).
148                         if (!WSSConstants.TAG_xenc_EncryptedData.equals(xmlSecEndElement.getName())) {
149                             engageSecurityHeaderHandler(subInputProcessorChain, getSecurityProperties(),
150                                     xmlSecEventList, startIndexForProcessor, xmlSecEndElement.getName());
151                         }
152 
153                         // Check for multiple timestamps
154                         if (xmlSecEndElement.getName().equals(WSSConstants.TAG_WSU_TIMESTAMP)) {
155                             if (timestampFound) {
156                                 WSInboundSecurityContext context =
157                                     (WSInboundSecurityContext)subInputProcessorChain.getSecurityContext();
158                                 context.handleBSPRule(BSPRule.R3227);
159                             }
160                             timestampFound = true;
161                         }
162                     }
163                     break;
164             }
165 
166         } while (!(xmlSecEvent.getEventType() == XMLStreamConstants.START_ELEMENT
167                 && xmlSecEvent.asStartElement().getName().getLocalPart().equals(WSSConstants.TAG_SOAP_BODY_LN)
168                 && xmlSecEvent.asStartElement().getName().getNamespaceURI().equals(
169                 WSSUtils.getSOAPMessageVersionNamespace(xmlSecEvent.asStartElement()))
170         ));
171         //if we reach this state we didn't find a security header
172         //issue a security event to notify about this fact:
173         NoSecuritySecurityEvent noSecuritySecurityEvent = new NoSecuritySecurityEvent();
174         noSecuritySecurityEvent.setCorrelationID(IDGenerator.generateID(null));
175         inputProcessorChain.getSecurityContext().registerSecurityEvent(noSecuritySecurityEvent);
176 
177         return finalizeHeaderProcessing(
178                 inputProcessorChain, subInputProcessorChain,
179                 internalSecurityHeaderBufferProcessor, xmlSecEventList);
180     }
181 
182     private XMLSecEvent finalizeHeaderProcessing(
183             InputProcessorChain originalInputProcessorChain,
184             InputProcessorChain subInputProcessorChain,
185             InternalSecurityHeaderBufferProcessor internalSecurityHeaderBufferProcessor,
186             Deque<XMLSecEvent> xmlSecEventList) {
187 
188         subInputProcessorChain.removeProcessor(internalSecurityHeaderBufferProcessor);
189         subInputProcessorChain.addProcessor(
190                 new InternalSecurityHeaderReplayProcessor(getSecurityProperties()));
191 
192         //remove this processor from chain now. the next events will go directly to the other processors
193         subInputProcessorChain.removeProcessor(this);
194         //since we cloned the inputProcessor list we have to add the processors from
195         //the subChain to the main chain.
196         originalInputProcessorChain.getProcessors().clear();
197         originalInputProcessorChain.getProcessors().addAll(subInputProcessorChain.getProcessors());
198 
199         //return first event now;
200         return xmlSecEventList.pollLast();
201     }
202 
203     @SuppressWarnings("unchecked")
204     private void engageSecurityHeaderHandler(InputProcessorChain inputProcessorChain,
205                                              XMLSecurityProperties securityProperties,
206                                              Deque<XMLSecEvent> eventQueue,
207                                              Integer index,
208                                              QName elementName)
209             throws WSSecurityException, XMLStreamException {
210 
211         Class<XMLSecurityHeaderHandler> clazz =
212             (Class<XMLSecurityHeaderHandler>)SecurityHeaderHandlerMapper.getSecurityHeaderHandler(elementName);
213         if (clazz == null) {
214             LOG.warn("No matching handler found for " + elementName);
215             return;
216         }
217         try {
218             XMLSecurityHeaderHandler xmlSecurityHeaderHandler = clazz.getDeclaredConstructor().newInstance();
219             xmlSecurityHeaderHandler.handle(inputProcessorChain, securityProperties, eventQueue, index);
220         } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
221             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
222         } catch (WSSecurityException e) {
223             throw e;
224         } catch (XMLSecurityException e) {
225             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
226         }
227     }
228 
229     /**
230      * Temporary Processor to buffer all events until the end of the security header
231      */
232     public class InternalSecurityHeaderBufferProcessor extends AbstractInputProcessor {
233 
234         InternalSecurityHeaderBufferProcessor(XMLSecurityProperties securityProperties) {
235             super(securityProperties);
236             setPhase(WSSConstants.Phase.POSTPROCESSING);
237             addBeforeProcessor(SecurityHeaderInputProcessor.class.getName());
238         }
239 
240         @Override
241         public XMLSecEvent processHeaderEvent(InputProcessorChain inputProcessorChain)
242                 throws XMLStreamException, XMLSecurityException {
243             XMLSecEvent xmlSecEvent = inputProcessorChain.processHeaderEvent();
244             xmlSecEventList.push(xmlSecEvent);
245             return xmlSecEvent;
246         }
247 
248         @Override
249         public XMLSecEvent processEvent(InputProcessorChain inputProcessorChain)
250                 throws XMLStreamException, XMLSecurityException {
251             //should never be called because we remove this processor before
252             return null;
253         }
254     }
255 
256     /**
257      * Temporary processor to replay the buffered events
258      */
259     public class InternalSecurityHeaderReplayProcessor extends AbstractInputProcessor {
260 
261         public InternalSecurityHeaderReplayProcessor(XMLSecurityProperties securityProperties) {
262             super(securityProperties);
263             setPhase(WSSConstants.Phase.PREPROCESSING);
264             addBeforeProcessor(SecurityHeaderInputProcessor.class.getName());
265             addAfterProcessor(XMLEventReaderInputProcessor.class.getName());
266         }
267 
268         @Override
269         public XMLSecEvent processHeaderEvent(InputProcessorChain inputProcessorChain)
270                 throws XMLStreamException, XMLSecurityException {
271             return null;
272         }
273 
274         @Override
275         public XMLSecEvent processEvent(InputProcessorChain inputProcessorChain)
276                 throws XMLStreamException, XMLSecurityException {
277 
278             if (!xmlSecEventList.isEmpty()) {
279                 return xmlSecEventList.pollLast();
280             } else {
281                 inputProcessorChain.removeProcessor(this);
282                 return inputProcessorChain.processEvent();
283             }
284         }
285     }
286 }