/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.wss4j.stax.impl.processor.output;
import java.util.ArrayDeque;
import java.util.Comparator;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.stax.ext.WSSConstants;
import org.apache.wss4j.stax.ext.WSSSecurityProperties;
import org.apache.wss4j.stax.impl.SecurityHeaderOrder;
import org.apache.wss4j.stax.utils.WSSUtils;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.stax.ext.AbstractOutputProcessor;
import org.apache.xml.security.stax.ext.OutputProcessorChain;
import org.apache.xml.security.stax.ext.XMLSecurityConstants;
import org.apache.xml.security.stax.ext.stax.XMLSecEvent;
import org.apache.xml.security.stax.ext.stax.XMLSecStartElement;
import org.apache.xml.security.stax.impl.processor.output.FinalOutputProcessor;
/**
* The basic ordering (token dependencies) is given through the processor order
* but we have more ordering criterias e.g. signed timestamp and strict header ordering ws-policy.
* To be able to sign a timestamp the processor must be inserted before the signature processor but that
* means that the timestamp is below the signature in the sec-header. Because of the highly dynamic nature
* of the processor chain (and encryption makes it far more worse) we have to order the headers afterwards.
* So that is what this processor does, the final header reordering...
*/
public class SecurityHeaderReorderProcessor extends AbstractOutputProcessor {
private final Map<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>> actionEventMap =
new LinkedHashMap<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>>();
private int securityHeaderIndex;
private Deque<XMLSecEvent> currentDeque;
public SecurityHeaderReorderProcessor() throws XMLSecurityException {
super();
setPhase(XMLSecurityConstants.Phase.POSTPROCESSING);
addBeforeProcessor(FinalOutputProcessor.class.getName());
}
@Override
public void init(OutputProcessorChain outputProcessorChain) throws XMLSecurityException {
super.init(outputProcessorChain);
List<XMLSecurityConstants.Action> outActions = getSecurityProperties().getActions();
for (int i = outActions.size() - 1; i >= 0; i--) {
XMLSecurityConstants.Action outAction = outActions.get(i);
actionEventMap.put(outAction, new TreeMap<SecurityHeaderOrder, Deque<XMLSecEvent>>(new Comparator<SecurityHeaderOrder>() {
@Override
public int compare(SecurityHeaderOrder o1, SecurityHeaderOrder o2) {
if (WSSConstants.TAG_dsig_Signature.equals(o1.getSecurityHeaderElementName())) {
return 1;
} else if (WSSConstants.TAG_dsig_Signature.equals(o2.getSecurityHeaderElementName())) {
return -1;
}
return 1;
}
}));
}
}
@Override
public void processEvent(XMLSecEvent xmlSecEvent, OutputProcessorChain outputProcessorChain)
throws XMLStreamException, XMLSecurityException {
int documentLevel = xmlSecEvent.getDocumentLevel();
if (documentLevel < 3
|| !WSSUtils.isInSecurityHeader(xmlSecEvent, ((WSSSecurityProperties) getSecurityProperties()).getActor())) {
outputProcessorChain.processEvent(xmlSecEvent);
return;
}
//now we are in our security header
if (documentLevel == 3) {
if (xmlSecEvent.isEndElement() && xmlSecEvent.asEndElement().getName().equals(WSSConstants.TAG_WSSE_SECURITY)) {
OutputProcessorChain subOutputProcessorChain = outputProcessorChain.createSubChain(this);
Iterator<Map.Entry<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>>> iterator =
actionEventMap.entrySet().iterator();
loop:
while (iterator.hasNext()) {
Map.Entry<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>> next = iterator.next();
boolean encryptAction = false;
Iterator<Map.Entry<SecurityHeaderOrder, Deque<XMLSecEvent>>> entryIterator = next.getValue().entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<SecurityHeaderOrder, Deque<XMLSecEvent>> entry = entryIterator.next();
//output all non encrypted headers until...
if (!entry.getKey().isEncrypted()) {
Deque<XMLSecEvent> xmlSecEvents = entry.getValue();
while (!xmlSecEvents.isEmpty()) {
XMLSecEvent event = xmlSecEvents.pop();
subOutputProcessorChain.reset();
subOutputProcessorChain.processEvent(event);
}
//remove the actual header so that it won't be output twice in the loop below
entryIterator.remove();
}
//... the action is encryption and...
if (entry.getKey().getAction().getName().contains("Encrypt")) {
encryptAction = true;
}
}
//...output the rest of the encrypt action and...
if (encryptAction) {
break loop;
}
}
//...loop again over the headers and output the leftover headers
iterator = actionEventMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<XMLSecurityConstants.Action, Map<SecurityHeaderOrder, Deque<XMLSecEvent>>> next = iterator.next();
Iterator<Map.Entry<SecurityHeaderOrder, Deque<XMLSecEvent>>> entryIterator = next.getValue().entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<SecurityHeaderOrder, Deque<XMLSecEvent>> entry = entryIterator.next();
Deque<XMLSecEvent> xmlSecEvents = entry.getValue();
while (!xmlSecEvents.isEmpty()) {
XMLSecEvent event = xmlSecEvents.pop();
subOutputProcessorChain.reset();
subOutputProcessorChain.processEvent(event);
}
}
}
outputProcessorChain.removeProcessor(this);
}
outputProcessorChain.processEvent(xmlSecEvent);
return;
} else if (documentLevel == 4) {
switch (xmlSecEvent.getEventType()) {
case XMLStreamConstants.START_ELEMENT:
XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement();
List<SecurityHeaderOrder> securityHeaderOrderList =
outputProcessorChain.getSecurityContext().getAsList(SecurityHeaderOrder.class);
SecurityHeaderOrder securityHeaderOrder = securityHeaderOrderList.get(securityHeaderIndex);
if (!xmlSecStartElement.getName().equals(WSSConstants.TAG_xenc_EncryptedData)
&& !xmlSecStartElement.getName().equals(securityHeaderOrder.getSecurityHeaderElementName())) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, "empty",
new Object[] {"Invalid security header order. Expected "
+ securityHeaderOrder.getSecurityHeaderElementName()
+ " but got " + xmlSecStartElement.getName()});
}
Map<SecurityHeaderOrder, Deque<XMLSecEvent>> map = actionEventMap.get(securityHeaderOrder.getAction());
currentDeque = new ArrayDeque<XMLSecEvent>();
map.put(securityHeaderOrder, currentDeque);
securityHeaderIndex++;
break;
}
}
currentDeque.offer(xmlSecEvent);
}
}