/** * 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.cxf.ws.security.wss4j; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.security.Provider; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor; import org.apache.cxf.interceptor.AttachmentOutInterceptor; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.interceptor.StaxOutInterceptor; import org.apache.cxf.message.Exchange; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import org.apache.cxf.rt.security.utils.SecurityUtils; import org.apache.cxf.ws.security.SecurityConstants; import org.apache.wss4j.common.ConfigurationConstants; import org.apache.wss4j.common.WSSPolicyException; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.crypto.ThreadLocalSecurityProvider; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.stax.ext.WSSSecurityProperties; import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants; import org.apache.wss4j.stax.setup.ConfigurationConverter; import org.apache.wss4j.stax.setup.OutboundWSSec; import org.apache.wss4j.stax.setup.WSSec; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.stax.ext.OutboundSecurityContext; import org.apache.xml.security.stax.impl.OutboundSecurityContextImpl; import org.apache.xml.security.stax.securityEvent.SecurityEvent; import org.apache.xml.security.stax.securityEvent.SecurityEventListener; import org.apache.xml.security.stax.securityEvent.TokenSecurityEvent; public class WSS4JStaxOutInterceptor extends AbstractWSS4JStaxInterceptor { public static final String OUTPUT_STREAM_HOLDER = WSS4JStaxOutInterceptor.class.getName() + ".outputstream"; private static final Logger LOG = LogUtils.getL7dLogger(WSS4JStaxOutInterceptor.class); private WSS4JStaxOutInterceptorInternal ending; private boolean mtomEnabled; public WSS4JStaxOutInterceptor(WSSSecurityProperties securityProperties) { super(securityProperties); setPhase(Phase.PRE_STREAM); getBefore().add(StaxOutInterceptor.class.getName()); ending = createEndingInterceptor(); } public WSS4JStaxOutInterceptor(Map<String, Object> props) { super(props); setPhase(Phase.PRE_STREAM); getBefore().add(StaxOutInterceptor.class.getName()); getAfter().add("org.apache.cxf.interceptor.LoggingOutInterceptor"); getAfter().add("org.apache.cxf.ext.logging.LoggingOutInterceptor"); ending = createEndingInterceptor(); } public WSS4JStaxOutInterceptor() { super(); setPhase(Phase.PRE_STREAM); getBefore().add(StaxOutInterceptor.class.getName()); getAfter().add("org.apache.cxf.interceptor.LoggingOutInterceptor"); getAfter().add("org.apache.cxf.ext.logging.LoggingOutInterceptor"); ending = createEndingInterceptor(); } public boolean isAllowMTOM() { return mtomEnabled; } /** * Enable or disable mtom with WS-Security. MTOM is disabled if we are signing or * encrypting the message Body, as otherwise attachments would not get encrypted * or be part of the signature. * @param mtomEnabled */ public void setAllowMTOM(boolean allowMTOM) { this.mtomEnabled = allowMTOM; } @Override public Object getProperty(Object msgContext, String key) { return super.getProperty(msgContext, key); } protected void handleSecureMTOM(SoapMessage mc, WSSSecurityProperties secProps) { if (mtomEnabled) { return; } //must turn off mtom when using WS-Sec so binary is inlined so it can //be properly signed/encrypted/etc... String mtomKey = org.apache.cxf.message.Message.MTOM_ENABLED; if (Boolean.TRUE.equals(mc.get(mtomKey))) { LOG.warning("MTOM will be disabled as the WSS4JOutInterceptor.mtomEnabled property" + " is set to false"); } mc.put(mtomKey, Boolean.FALSE); } public void handleMessage(SoapMessage mc) throws Fault { OutputStream os = mc.getContent(OutputStream.class); String encoding = getEncoding(mc); XMLStreamWriter newXMLStreamWriter; try { WSSSecurityProperties secProps = createSecurityProperties(); translateProperties(mc, secProps); configureCallbackHandler(mc, secProps); final OutboundSecurityContext outboundSecurityContext = new OutboundSecurityContextImpl(); configureProperties(mc, outboundSecurityContext, secProps); if (secProps.getActions() == null || secProps.getActions().size() == 0) { // If no actions configured then return return; } handleSecureMTOM(mc, secProps); if (secProps.getAttachmentCallbackHandler() == null) { secProps.setAttachmentCallbackHandler(new AttachmentCallbackHandler(mc)); } SecurityEventListener securityEventListener = configureSecurityEventListener(mc, secProps); OutboundWSSec outboundWSSec = WSSec.getOutboundWSSec(secProps); @SuppressWarnings("unchecked") final List<SecurityEvent> requestSecurityEvents = (List<SecurityEvent>) mc.getExchange().get(SecurityEvent.class.getName() + ".in"); outboundSecurityContext.putList(SecurityEvent.class, requestSecurityEvents); outboundSecurityContext.addSecurityEventListener(securityEventListener); newXMLStreamWriter = outboundWSSec.processOutMessage(os, encoding, outboundSecurityContext); mc.setContent(XMLStreamWriter.class, newXMLStreamWriter); } catch (WSSecurityException e) { throw new Fault(e); } catch (WSSPolicyException e) { throw new Fault(e); } mc.put(AbstractOutDatabindingInterceptor.DISABLE_OUTPUTSTREAM_OPTIMIZATION, Boolean.TRUE); try { newXMLStreamWriter.writeStartDocument(encoding, "1.0"); } catch (XMLStreamException e) { throw new Fault(e); } mc.removeContent(OutputStream.class); mc.put(OUTPUT_STREAM_HOLDER, os); // Add a final interceptor to write end elements mc.getInterceptorChain().add(ending); } protected SecurityEventListener configureSecurityEventListener( final SoapMessage msg, WSSSecurityProperties securityProperties ) throws WSSPolicyException { final List<SecurityEvent> outgoingSecurityEventList = new LinkedList<>(); msg.getExchange().put(SecurityEvent.class.getName() + ".out", outgoingSecurityEventList); msg.put(SecurityEvent.class.getName() + ".out", outgoingSecurityEventList); final SecurityEventListener securityEventListener = new SecurityEventListener() { @Override public void registerSecurityEvent(SecurityEvent securityEvent) throws XMLSecurityException { if (securityEvent.getSecurityEventType() == WSSecurityEventConstants.SAML_TOKEN) { // Store SAML keys in case we need them on the inbound side TokenSecurityEvent<?> tokenSecurityEvent = (TokenSecurityEvent<?>)securityEvent; WSS4JUtils.parseAndStoreStreamingSecurityToken(tokenSecurityEvent.getSecurityToken(), msg); } else if (securityEvent.getSecurityEventType() == WSSecurityEventConstants.SignatureValue) { // Required for Signature Confirmation outgoingSecurityEventList.add(securityEvent); } } }; return securityEventListener; } protected void configureProperties( SoapMessage msg, OutboundSecurityContext outboundSecurityContext, WSSSecurityProperties securityProperties ) throws WSSecurityException { String user = (String)SecurityUtils.getSecurityPropertyValue(SecurityConstants.USERNAME, msg); if (user != null) { securityProperties.setTokenUser(user); } String sigUser = (String)SecurityUtils.getSecurityPropertyValue(SecurityConstants.SIGNATURE_USERNAME, msg); if (sigUser != null) { securityProperties.setSignatureUser(sigUser); } String encUser = (String)SecurityUtils.getSecurityPropertyValue(SecurityConstants.ENCRYPT_USERNAME, msg); if (encUser != null) { securityProperties.setEncryptionUser(encUser); } // Crypto loading only applies for Map Map<String, Object> config = getProperties(); if (config != null && !config.isEmpty()) { Crypto sigCrypto = loadCrypto( msg, ConfigurationConstants.SIG_PROP_FILE, ConfigurationConstants.SIG_PROP_REF_ID, securityProperties ); if (sigCrypto != null) { config.put(ConfigurationConstants.SIG_PROP_REF_ID, "RefId-" + sigCrypto.hashCode()); config.put("RefId-" + sigCrypto.hashCode(), sigCrypto); if (sigUser == null && sigCrypto.getDefaultX509Identifier() != null) { // Fall back to default identifier securityProperties.setSignatureUser(sigCrypto.getDefaultX509Identifier()); } } Crypto encCrypto = loadCrypto( msg, ConfigurationConstants.ENC_PROP_FILE, ConfigurationConstants.ENC_PROP_REF_ID, securityProperties ); if (encCrypto != null) { config.put(ConfigurationConstants.ENC_PROP_REF_ID, "RefId-" + encCrypto.hashCode()); config.put("RefId-" + encCrypto.hashCode(), encCrypto); if (encUser == null && encCrypto.getDefaultX509Identifier() != null) { // Fall back to default identifier securityProperties.setEncryptionUser(encCrypto.getDefaultX509Identifier()); } } ConfigurationConverter.parseCrypto(config, securityProperties); } else { Crypto sigCrypto = securityProperties.getSignatureCrypto(); if (sigCrypto != null && sigUser == null && sigCrypto.getDefaultX509Identifier() != null) { // Fall back to default identifier securityProperties.setSignatureUser(sigCrypto.getDefaultX509Identifier()); } Crypto encrCrypto = securityProperties.getEncryptionCrypto(); if (encrCrypto != null && encUser == null && encrCrypto.getDefaultX509Identifier() != null) { // Fall back to default identifier securityProperties.setEncryptionUser(encrCrypto.getDefaultX509Identifier()); } } if (securityProperties.getSignatureUser() == null && user != null) { securityProperties.setSignatureUser(user); } if (securityProperties.getEncryptionUser() == null && user != null) { securityProperties.setEncryptionUser(user); } } public final WSS4JStaxOutInterceptorInternal createEndingInterceptor() { return new WSS4JStaxOutInterceptorInternal(); } private String getEncoding(Message message) { Exchange ex = message.getExchange(); String encoding = (String) message.get(Message.ENCODING); if (encoding == null && ex.getInMessage() != null) { encoding = (String) ex.getInMessage().get(Message.ENCODING); message.put(Message.ENCODING, encoding); } if (encoding == null) { encoding = StandardCharsets.UTF_8.name(); message.put(Message.ENCODING, encoding); } return encoding; } final class WSS4JStaxOutInterceptorInternal extends AbstractPhaseInterceptor<Message> { WSS4JStaxOutInterceptorInternal() { super(Phase.PRE_STREAM_ENDING); getBefore().add(AttachmentOutInterceptor.AttachmentOutEndingInterceptor.class.getName()); } public void handleMessage(Message message) throws Fault { Object provider = message.getExchange().get(Provider.class); final boolean useCustomProvider = provider != null && ThreadLocalSecurityProvider.isInstalled(); try { if (useCustomProvider) { ThreadLocalSecurityProvider.setProvider((Provider)provider); } handleMessageInternal(message); } finally { if (useCustomProvider) { ThreadLocalSecurityProvider.unsetProvider(); } } } private void handleMessageInternal(Message mc) throws Fault { try { XMLStreamWriter xtw = mc.getContent(XMLStreamWriter.class); if (xtw != null) { xtw.writeEndDocument(); xtw.flush(); xtw.close(); } OutputStream os = (OutputStream) mc.get(OUTPUT_STREAM_HOLDER); if (os != null) { mc.setContent(OutputStream.class, os); } mc.removeContent(XMLStreamWriter.class); } catch (XMLStreamException e) { throw new Fault(e); } } } }