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.output;
20  
21  import java.util.ArrayDeque;
22  import java.util.Comparator;
23  import java.util.Deque;
24  import java.util.Iterator;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.TreeMap;
29  
30  import javax.xml.stream.XMLStreamConstants;
31  import javax.xml.stream.XMLStreamException;
32  
33  import org.apache.wss4j.common.ext.WSSecurityException;
34  import org.apache.wss4j.stax.ext.WSSConstants;
35  import org.apache.wss4j.stax.ext.WSSSecurityProperties;
36  import org.apache.wss4j.stax.impl.SecurityHeaderOrder;
37  import org.apache.wss4j.stax.utils.WSSUtils;
38  import org.apache.xml.security.exceptions.XMLSecurityException;
39  import org.apache.xml.security.stax.ext.AbstractOutputProcessor;
40  import org.apache.xml.security.stax.ext.OutputProcessorChain;
41  import org.apache.xml.security.stax.ext.XMLSecurityConstants;
42  import org.apache.xml.security.stax.ext.stax.XMLSecEvent;
43  import org.apache.xml.security.stax.ext.stax.XMLSecStartElement;
44  import org.apache.xml.security.stax.impl.processor.output.FinalOutputProcessor;
45  
46  /**
47   * The basic ordering (token dependencies) is given through the processor order
48   * but we have more ordering criterias e.g. signed timestamp and strict header ordering ws-policy.
49   * To be able to sign a timestamp the processor must be inserted before the signature processor but that
50   * means that the timestamp is below the signature in the sec-header. Because of the highly dynamic nature
51   * of the processor chain (and encryption makes it far more worse) we have to order the headers afterwards.
52   * So that is what this processor does, the final header reordering...
53   */
54  public class SecurityHeaderReorderProcessor extends AbstractOutputProcessor {
55  
56      private final Map<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>> actionEventMap =
57              new LinkedHashMap<>();
58  
59      private int securityHeaderIndex;
60      private Deque<XMLSecEvent> currentDeque;
61  
62      public SecurityHeaderReorderProcessor() throws XMLSecurityException {
63          super();
64          setPhase(XMLSecurityConstants.Phase.POSTPROCESSING);
65          addBeforeProcessor(FinalOutputProcessor.class);
66      }
67  
68      @Override
69      public void init(OutputProcessorChain outputProcessorChain) throws XMLSecurityException {
70          super.init(outputProcessorChain);
71  
72          List<XMLSecurityConstants.Action> outActions = getSecurityProperties().getActions();
73          for (int i = outActions.size() - 1; i >= 0; i--) {
74              XMLSecurityConstants.Action outAction = outActions.get(i);
75              actionEventMap.put(outAction, new TreeMap<SecurityHeaderOrder, Deque<XMLSecEvent>>(new Comparator<SecurityHeaderOrder>() {
76                  @Override
77                  public int compare(SecurityHeaderOrder o1, SecurityHeaderOrder o2) {
78                      if (WSSConstants.TAG_dsig_Signature.equals(o1.getSecurityHeaderElementName())) {
79                          return 1;
80                      } else if (WSSConstants.TAG_dsig_Signature.equals(o2.getSecurityHeaderElementName())) {
81                          return -1;
82                      }
83                      return 1;
84                  }
85              }));
86          }
87      }
88  
89      @Override
90      public void processEvent(XMLSecEvent xmlSecEvent, OutputProcessorChain outputProcessorChain)
91          throws XMLStreamException, XMLSecurityException {
92  
93          int documentLevel = xmlSecEvent.getDocumentLevel();
94          if (documentLevel < 3
95              || !WSSUtils.isInSecurityHeader(xmlSecEvent, ((WSSSecurityProperties) getSecurityProperties()).getActor())) {
96              outputProcessorChain.processEvent(xmlSecEvent);
97              return;
98          }
99  
100         //now we are in our security header
101 
102         if (documentLevel == 3) {
103             if (xmlSecEvent.isEndElement() && xmlSecEvent.asEndElement().getName().equals(WSSConstants.TAG_WSSE_SECURITY)) {
104                 OutputProcessorChain subOutputProcessorChain = outputProcessorChain.createSubChain(this);
105 
106                 Iterator<Map.Entry<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>>> iterator =
107                     actionEventMap.entrySet().iterator();
108                 loop:
109                 while (iterator.hasNext()) {
110                     Map.Entry<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>> next = iterator.next();
111 
112                     boolean encryptAction = false;
113                     Iterator<Map.Entry<SecurityHeaderOrder, Deque<XMLSecEvent>>> entryIterator = next.getValue().entrySet().iterator();
114                     while (entryIterator.hasNext()) {
115                         Map.Entry<SecurityHeaderOrder, Deque<XMLSecEvent>> entry = entryIterator.next();
116                         //output all non encrypted headers until...
117                         if (!entry.getKey().isEncrypted()) {
118                             Deque<XMLSecEvent> xmlSecEvents = entry.getValue();
119                             while (!xmlSecEvents.isEmpty()) {
120                                 XMLSecEvent event = xmlSecEvents.pop();
121                                 subOutputProcessorChain.reset();
122                                 subOutputProcessorChain.processEvent(event);
123                             }
124                             //remove the actual header so that it won't be output twice in the loop below
125                             entryIterator.remove();
126                         }
127                         //... the action is encryption and...
128                         if (entry.getKey().getAction().getName().contains("Encrypt")) {
129                             encryptAction = true;
130                         }
131                     }
132                     //...output the rest of the encrypt action and...
133                     if (encryptAction) {
134                         break loop;
135                     }
136                 }
137                 //...loop again over the headers and output the leftover headers
138                 iterator = actionEventMap.entrySet().iterator();
139                 while (iterator.hasNext()) {
140                     Map.Entry<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>> next = iterator.next();
141                     Iterator<Map.Entry<SecurityHeaderOrder, Deque<XMLSecEvent>>> entryIterator = next.getValue().entrySet().iterator();
142                     while (entryIterator.hasNext()) {
143                         Map.Entry<SecurityHeaderOrder, Deque<XMLSecEvent>> entry = entryIterator.next();
144                         Deque<XMLSecEvent> xmlSecEvents = entry.getValue();
145                         while (!xmlSecEvents.isEmpty()) {
146                             XMLSecEvent event = xmlSecEvents.pop();
147                             subOutputProcessorChain.reset();
148                             subOutputProcessorChain.processEvent(event);
149                         }
150                     }
151                 }
152                 outputProcessorChain.removeProcessor(this);
153             }
154             outputProcessorChain.processEvent(xmlSecEvent);
155             return;
156         } else if (documentLevel == 4 && XMLStreamConstants.START_ELEMENT == xmlSecEvent.getEventType()) {
157             XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement();
158 
159             List<SecurityHeaderOrder> securityHeaderOrderList =
160                     outputProcessorChain.getSecurityContext().getAsList(SecurityHeaderOrder.class);
161             SecurityHeaderOrder securityHeaderOrder = securityHeaderOrderList.get(securityHeaderIndex);
162             if (!xmlSecStartElement.getName().equals(WSSConstants.TAG_xenc_EncryptedData)
163                     && !xmlSecStartElement.getName().equals(securityHeaderOrder.getSecurityHeaderElementName())) {
164                 throw new WSSecurityException(
165                         WSSecurityException.ErrorCode.FAILURE, "empty",
166                         new Object[]{"Invalid security header order. Expected "
167                                 + securityHeaderOrder.getSecurityHeaderElementName()
168                                 + " but got " + xmlSecStartElement.getName()});
169             }
170 
171             Map<SecurityHeaderOrder, Deque<XMLSecEvent>> map = actionEventMap.get(securityHeaderOrder.getAction());
172             currentDeque = new ArrayDeque<>();
173             map.put(securityHeaderOrder, currentDeque);
174 
175             securityHeaderIndex++;
176         }
177         currentDeque.offer(xmlSecEvent);
178     }
179 }