org.apache.cxf.ws.security.wss4j.StaxCryptoCoverageChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cxf-rt-ws-security Show documentation
Show all versions of cxf-rt-ws-security Show documentation
Apache CXF Runtime WS Security
/**
* 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.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapFault;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.SoapVersion;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.ws.addressing.AddressingProperties;
import org.apache.cxf.ws.addressing.Names;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants;
import org.apache.xml.security.stax.securityEvent.AbstractSecuredElementSecurityEvent;
import org.apache.xml.security.stax.securityEvent.SecurityEvent;
import org.apache.xml.security.stax.securityEvent.SecurityEventConstants.Event;
/**
* This interceptor handles parsing the StaX WS-Security results (events) + checks that the
* specified crypto coverage events actually occurred. The default functionality is to enforce
* that the SOAP Body, Timestamp, and WS-Addressing ReplyTo and FaultTo headers must be signed,
* and the UsernameToken must be encrypted (if they exist in the message payload).
*
* Note that this interceptor must be explicitly added to the InInterceptor chain.
*/
public class StaxCryptoCoverageChecker extends AbstractPhaseInterceptor {
public static final String SOAP_NS = WSConstants.URI_SOAP11_ENV;
public static final String SOAP12_NS = WSConstants.URI_SOAP12_ENV;
public static final String WSU_NS = WSConstants.WSU_NS;
public static final String WSSE_NS = WSConstants.WSSE_NS;
public static final String WSA_NS = Names.WSA_NAMESPACE_NAME;
private boolean signBody;
private boolean signTimestamp;
private boolean encryptBody;
private boolean signAddressingHeaders;
private boolean signUsernameToken;
private boolean encryptUsernameToken;
public StaxCryptoCoverageChecker() {
super(Phase.PRE_PROTOCOL);
// Sign SOAP Body
setSignBody(true);
// Sign Timestamp
setSignTimestamp(true);
// Sign Addressing Headers
setSignAddressingHeaders(true);
// Encrypt UsernameToken
setEncryptUsernameToken(true);
}
@Override
public void handleMessage(SoapMessage soapMessage) throws Fault {
@SuppressWarnings("unchecked")
final List incomingSecurityEventList =
(List)soapMessage.get(SecurityEvent.class.getName() + ".in");
List results = new ArrayList<>();
if (incomingSecurityEventList != null) {
// Get all Signed/Encrypted Results
results.addAll(
getEventFromResults(WSSecurityEventConstants.SIGNED_PART, incomingSecurityEventList));
results.addAll(
getEventFromResults(WSSecurityEventConstants.SignedElement, incomingSecurityEventList));
if (encryptBody || encryptUsernameToken) {
results.addAll(
getEventFromResults(WSSecurityEventConstants.ENCRYPTED_PART, incomingSecurityEventList));
results.addAll(
getEventFromResults(WSSecurityEventConstants.EncryptedElement, incomingSecurityEventList));
}
}
try {
checkSignedBody(results);
checkEncryptedBody(results);
if (signTimestamp) {
// We only insist on the Timestamp being signed if it is actually present in the message
List timestampResults =
getEventFromResults(WSSecurityEventConstants.TIMESTAMP, incomingSecurityEventList);
if (!timestampResults.isEmpty()) {
checkSignedTimestamp(results);
}
}
if (signAddressingHeaders) {
AddressingProperties addressingProperties =
(AddressingProperties)soapMessage.get("javax.xml.ws.addressing.context.inbound");
checkSignedAddressing(results, addressingProperties);
}
if (signUsernameToken || encryptUsernameToken) {
// We only insist on the UsernameToken being signed/encrypted if it is actually
// present in the message
List usernameTokenResults =
getEventFromResults(WSSecurityEventConstants.USERNAME_TOKEN, incomingSecurityEventList);
if (!usernameTokenResults.isEmpty()) {
if (signUsernameToken) {
checkSignedUsernameToken(results);
}
if (encryptUsernameToken) {
checkEncryptedUsernameToken(results);
}
}
}
} catch (WSSecurityException e) {
throw createSoapFault(soapMessage.getVersion(), e);
}
}
private List getEventFromResults(Event event, List incomingSecurityEventList) {
List results = new ArrayList<>();
for (SecurityEvent incomingEvent : incomingSecurityEventList) {
if (event == incomingEvent.getSecurityEventType()) {
results.add(incomingEvent);
}
}
return results;
}
private void checkSignedBody(List results) throws WSSecurityException {
if (!signBody) {
return;
}
boolean isBodySigned = false;
for (SecurityEvent signedEvent : results) {
AbstractSecuredElementSecurityEvent securedEvent =
(AbstractSecuredElementSecurityEvent)signedEvent;
if (!securedEvent.isSigned()) {
continue;
}
List signedPath = securedEvent.getElementPath();
if (isBody(signedPath)) {
isBodySigned = true;
break;
}
}
if (!isBodySigned) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
new Exception("The SOAP Body is not signed"));
}
}
private void checkEncryptedBody(List results) throws WSSecurityException {
if (!encryptBody) {
return;
}
boolean isBodyEncrypted = false;
for (SecurityEvent signedEvent : results) {
AbstractSecuredElementSecurityEvent securedEvent =
(AbstractSecuredElementSecurityEvent)signedEvent;
if (!securedEvent.isEncrypted()) {
continue;
}
List encryptedPath = securedEvent.getElementPath();
if (isBody(encryptedPath)) {
isBodyEncrypted = true;
break;
}
}
if (!isBodyEncrypted) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
new Exception("The SOAP Body is not encrypted"));
}
}
private void checkSignedTimestamp(List results) throws WSSecurityException {
if (!signTimestamp) {
return;
}
boolean isTimestampSigned = false;
for (SecurityEvent signedEvent : results) {
AbstractSecuredElementSecurityEvent securedEvent =
(AbstractSecuredElementSecurityEvent)signedEvent;
if (!securedEvent.isSigned()) {
continue;
}
List signedPath = securedEvent.getElementPath();
if (isTimestamp(signedPath)) {
isTimestampSigned = true;
break;
}
}
if (!isTimestampSigned) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
new Exception("The Timestamp is not signed"));
}
}
private void checkSignedAddressing(
List results,
AddressingProperties addressingProperties
) throws WSSecurityException {
if (!signAddressingHeaders || addressingProperties == null
|| (addressingProperties.getReplyTo() == null && addressingProperties.getFaultTo() == null)) {
return;
}
boolean isReplyToSigned = false;
boolean isFaultToSigned = false;
for (SecurityEvent signedEvent : results) {
AbstractSecuredElementSecurityEvent securedEvent =
(AbstractSecuredElementSecurityEvent)signedEvent;
if (!securedEvent.isSigned()) {
continue;
}
List signedPath = securedEvent.getElementPath();
if (isReplyTo(signedPath)) {
isReplyToSigned = true;
}
if (isFaultTo(signedPath)) {
isFaultToSigned = true;
}
if (isReplyToSigned && isFaultToSigned) {
break;
}
}
if (!isReplyToSigned && (addressingProperties.getReplyTo() != null)) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
new Exception("The Addressing headers are not signed"));
}
if (!isFaultToSigned && (addressingProperties.getFaultTo() != null)) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
new Exception("The Addressing headers are not signed"));
}
}
private void checkSignedUsernameToken(List results) throws WSSecurityException {
if (!signUsernameToken) {
return;
}
boolean isUsernameTokenSigned = false;
for (SecurityEvent signedEvent : results) {
AbstractSecuredElementSecurityEvent securedEvent =
(AbstractSecuredElementSecurityEvent)signedEvent;
if (!securedEvent.isSigned()) {
continue;
}
List signedPath = securedEvent.getElementPath();
if (isUsernameToken(signedPath)) {
isUsernameTokenSigned = true;
break;
}
}
if (!isUsernameTokenSigned) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
new Exception("The UsernameToken is not signed"));
}
}
private void checkEncryptedUsernameToken(List results) throws WSSecurityException {
if (!encryptUsernameToken) {
return;
}
boolean isUsernameTokenEncrypted = false;
for (SecurityEvent encryptedEvent : results) {
AbstractSecuredElementSecurityEvent securedEvent =
(AbstractSecuredElementSecurityEvent)encryptedEvent;
if (!securedEvent.isEncrypted()) {
continue;
}
List encryptedPath = securedEvent.getElementPath();
if (isUsernameToken(encryptedPath)) {
isUsernameTokenEncrypted = true;
break;
}
}
if (!isUsernameTokenEncrypted) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
new Exception("The UsernameToken is not encrypted"));
}
}
private boolean isEnvelope(QName qname) {
return "Envelope".equals(qname.getLocalPart())
&& (SOAP_NS.equals(qname.getNamespaceURI())
|| SOAP12_NS.equals(qname.getNamespaceURI()));
}
private boolean isSoapHeader(QName qname) {
return "Header".equals(qname.getLocalPart())
&& (SOAP_NS.equals(qname.getNamespaceURI())
|| SOAP12_NS.equals(qname.getNamespaceURI()));
}
private boolean isSecurityHeader(QName qname) {
return "Security".equals(qname.getLocalPart()) && WSSE_NS.equals(qname.getNamespaceURI());
}
private boolean isTimestamp(List qnames) {
return qnames != null
&& qnames.size() == 4
&& isEnvelope(qnames.get(0))
&& isSoapHeader(qnames.get(1))
&& isSecurityHeader(qnames.get(2))
&& "Timestamp".equals(qnames.get(3).getLocalPart())
&& WSU_NS.equals(qnames.get(3).getNamespaceURI());
}
private boolean isReplyTo(List qnames) {
return qnames != null && qnames.size() == 3
&& isEnvelope(qnames.get(0))
&& isSoapHeader(qnames.get(1))
&& "ReplyTo".equals(qnames.get(2).getLocalPart())
&& WSA_NS.equals(qnames.get(2).getNamespaceURI());
}
private boolean isFaultTo(List qnames) {
return qnames != null && qnames.size() == 3
&& isEnvelope(qnames.get(0))
&& isSoapHeader(qnames.get(1))
&& "FaultTo".equals(qnames.get(2).getLocalPart())
&& WSA_NS.equals(qnames.get(2).getNamespaceURI());
}
private boolean isBody(List qnames) {
return qnames != null && qnames.size() == 2
&& isEnvelope(qnames.get(0))
&& "Body".equals(qnames.get(1).getLocalPart())
&& (SOAP_NS.equals(qnames.get(1).getNamespaceURI())
|| SOAP12_NS.equals(qnames.get(1).getNamespaceURI()));
}
private boolean isUsernameToken(List qnames) {
return qnames != null && qnames.size() == 4
&& isEnvelope(qnames.get(0))
&& isSoapHeader(qnames.get(1))
&& isSecurityHeader(qnames.get(2))
&& "UsernameToken".equals(qnames.get(3).getLocalPart())
&& WSSE_NS.equals(qnames.get(3).getNamespaceURI());
}
public boolean isSignBody() {
return signBody;
}
public final void setSignBody(boolean signBody) {
this.signBody = signBody;
}
public boolean isSignTimestamp() {
return signTimestamp;
}
public final void setSignTimestamp(boolean signTimestamp) {
this.signTimestamp = signTimestamp;
}
public boolean isEncryptBody() {
return encryptBody;
}
public final void setEncryptBody(boolean encryptBody) {
this.encryptBody = encryptBody;
}
public boolean isSignAddressingHeaders() {
return signAddressingHeaders;
}
public final void setSignAddressingHeaders(boolean signAddressingHeaders) {
this.signAddressingHeaders = signAddressingHeaders;
}
/**
* Create a SoapFault from a WSSecurityException, following the SOAP Message Security
* 1.1 specification, chapter 12 "Error Handling".
*
* When the Soap version is 1.1 then set the Fault/Code/Value from the fault code
* specified in the WSSecurityException (if it exists).
*
* Otherwise set the Fault/Code/Value to env:Sender and the Fault/Code/Subcode/Value
* as the fault code from the WSSecurityException.
*/
private SoapFault createSoapFault(SoapVersion version, WSSecurityException e) {
SoapFault fault;
javax.xml.namespace.QName faultCode = e.getFaultCode();
if (version.getVersion() == 1.1 && faultCode != null) {
fault = new SoapFault(e.getMessage(), e, faultCode);
} else {
fault = new SoapFault(e.getMessage(), e, version.getSender());
if (version.getVersion() != 1.1 && faultCode != null) {
fault.setSubCode(faultCode);
}
}
return fault;
}
public boolean isSignUsernameToken() {
return signUsernameToken;
}
public void setSignUsernameToken(boolean signUsernameToken) {
this.signUsernameToken = signUsernameToken;
}
public boolean isEncryptUsernameToken() {
return encryptUsernameToken;
}
public final void setEncryptUsernameToken(boolean encryptUsernameToken) {
this.encryptUsernameToken = encryptUsernameToken;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy