diff --git a/parent/pom.xml b/parent/pom.xml index 282430a6..39329c0e 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -569,9 +569,9 @@ 1.3.0 - wss4j + org.apache.ws.security wss4j - 1.5.1 + 1.5.4 org.acegisecurity diff --git a/pom.xml b/pom.xml index 145076db..786753a1 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ http://static.springframework.org/spring-ws/site/index.html JIRA - http://opensource2.atlassian.com/projects/spring/browse/SWS/ + http://jira.springframework.org/browse/SWS bamboo @@ -49,6 +49,9 @@ Rick Evans + + Tareq Abed Rabbo + The Spring Web Services Framework diff --git a/security/pom.xml b/security/pom.xml index 5e75b5bb..2bf224ae 100644 --- a/security/pom.xml +++ b/security/pom.xml @@ -22,6 +22,11 @@ Spring External Dependencies Repository https://springframework.svn.sourceforge.net/svnroot/springframework/repos/repo-ext/ + + wso2 + WSO2 Repository + http://dist.wso2.org/maven2/ + @@ -103,7 +108,17 @@ saaj-impl provided - + + org.apache.ws.commons.axiom + axiom-api + true + + + org.apache.ws.commons.axiom + axiom-impl + true + + com.sun.xml.wss xws-security @@ -112,6 +127,12 @@ xml-security xmlsec + + + org.apache.ws.security + wss4j + + org.acegisecurity acegi-security diff --git a/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java b/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java index d19a59ac..b8790cc9 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java +++ b/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java @@ -52,7 +52,7 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter /** Logger available to subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); - private static final QName WS_SECURITY_NAME = + protected static final QName WS_SECURITY_NAME = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security"); private boolean secureResponse = true; @@ -88,20 +88,20 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter */ /** - * Validates a server-side incoming request. Delegates to {@link #validateMessage(SoapMessage)} if the {@link - * #setValidateRequest(boolean) validateRequest} property is true. + * Validates a server-side incoming request. Delegates to {@link #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)} + * if the {@link #setValidateRequest(boolean) validateRequest} property is true. * * @param messageContext the message context, containing the request to be validated * @param endpoint chosen endpoint to invoke * @return true if the request was valid; false otherwise. * @throws Exception in case of errors - * @see #validateMessage(SoapMessage) + * @see #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext) */ public final boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { if (validateRequest) { Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest()); try { - validateMessage((SoapMessage) messageContext.getRequest()); + validateMessage((SoapMessage) messageContext.getRequest(), messageContext); return true; } catch (WsSecurityValidationException ex) { @@ -117,21 +117,21 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter } /** - * Secures a server-side outgoing response. Delegates to {@link #secureMessage(SoapMessage)} if the {@link - * #setSecureResponse(boolean) secureResponse} property is true. + * Secures a server-side outgoing response. Delegates to {@link #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)} + * if the {@link #setSecureResponse(boolean) secureResponse} property is true. * * @param messageContext the message context, containing the response to be secured * @param endpoint chosen endpoint to invoke * @return true if the response was secured; false otherwise. * @throws Exception in case of errors - * @see #secureMessage(SoapMessage) + * @see #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext) */ public final boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { if (secureResponse) { Assert.isTrue(messageContext.hasResponse(), "MessageContext contains no response"); Assert.isInstanceOf(SoapMessage.class, messageContext.getResponse()); try { - secureMessage((SoapMessage) messageContext.getResponse()); + secureMessage((SoapMessage) messageContext.getResponse(), messageContext); return true; } catch (WsSecuritySecurementException ex) { @@ -160,19 +160,19 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter */ /** - * Secures a client-side outgoing request. Delegates to {@link #secureMessage(SoapMessage)} if the {@link - * #setSecureRequest(boolean) secureRequest} property is true. + * Secures a client-side outgoing request. Delegates to {@link #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)} + * if the {@link #setSecureRequest(boolean) secureRequest} property is true. * * @param messageContext the message context, containing the request to be secured * @return true if the response was secured; false otherwise. * @throws Exception in case of errors - * @see #secureMessage(SoapMessage) + * @see #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext) */ public final boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { if (secureRequest) { Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest()); try { - secureMessage((SoapMessage) messageContext.getRequest()); + secureMessage((SoapMessage) messageContext.getRequest(), messageContext); return true; } catch (WsSecuritySecurementException ex) { @@ -188,20 +188,20 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter } /** - * Validates a client-side incoming response. Delegates to {@link #validateMessage(SoapMessage)} if the {@link - * #setValidateResponse(boolean) validateResponse} property is true. + * Validates a client-side incoming response. Delegates to {@link #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)} + * if the {@link #setValidateResponse(boolean) validateResponse} property is true. * * @param messageContext the message context, containing the response to be validated * @return true if the request was valid; false otherwise. * @throws Exception in case of errors - * @see #validateMessage(SoapMessage) + * @see #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext) */ public final boolean handleResponse(MessageContext messageContext) throws WebServiceClientException { if (validateResponse) { Assert.isTrue(messageContext.hasResponse(), "MessageContext contains no response"); Assert.isInstanceOf(SoapMessage.class, messageContext.getResponse()); try { - validateMessage((SoapMessage) messageContext.getResponse()); + validateMessage((SoapMessage) messageContext.getResponse(), messageContext); return true; } catch (WsSecurityValidationException ex) { @@ -284,7 +284,8 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter * @param soapMessage the soap message to validate * @throws WsSecurityValidationException in case of validation errors */ - protected abstract void validateMessage(SoapMessage soapMessage) throws WsSecurityValidationException; + protected abstract void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException; /** * Abstract template method. Subclasses are required to secure the response contained in the given {@link @@ -293,5 +294,6 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter * @param soapMessage the soap message to secure * @throws WsSecuritySecurementException in case of securement errors */ - protected abstract void secureMessage(SoapMessage soapMessage) throws WsSecuritySecurementException; + protected abstract void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecuritySecurementException; } diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/AbstractCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/callback/AbstractCallbackHandler.java similarity index 96% rename from security/src/main/java/org/springframework/ws/soap/security/xwss/callback/AbstractCallbackHandler.java rename to security/src/main/java/org/springframework/ws/soap/security/callback/AbstractCallbackHandler.java index 1fbf5153..2ea793bc 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/AbstractCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/callback/AbstractCallbackHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ws.soap.security.xwss.callback; +package org.springframework.ws.soap.security.callback; import java.io.IOException; import javax.security.auth.callback.Callback; diff --git a/security/src/main/java/org/springframework/ws/soap/security/callback/CallbackHandlerChain.java b/security/src/main/java/org/springframework/ws/soap/security/callback/CallbackHandlerChain.java new file mode 100644 index 00000000..6004d24b --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/callback/CallbackHandlerChain.java @@ -0,0 +1,175 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.callback; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import com.sun.xml.wss.impl.callback.TimestampValidationCallback; + +/** + * Represents a chain of CallbackHandlers. For each callback, each of the handlers is called in term. If a + * handler throws a UnsupportedCallbackException, the next handler is tried. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +public class CallbackHandlerChain extends AbstractCallbackHandler { + + private CallbackHandler[] callbackHandlers; + + public CallbackHandlerChain(CallbackHandler[] callbackHandlers) { + this.callbackHandlers = callbackHandlers; + } + + public void setCallbackHandlers(CallbackHandler[] callbackHandlers) { + this.callbackHandlers = callbackHandlers; + } + + protected void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof CertificateValidationCallback) { + handleCertificateValidationCallback((CertificateValidationCallback) callback); + } + else if (callback instanceof PasswordValidationCallback) { + handlePasswordValidationCallback((PasswordValidationCallback) callback); + } + else if (callback instanceof TimestampValidationCallback) { + handleTimestampValidationCallback((TimestampValidationCallback) callback); + } + else { + boolean allUnsupported = true; + for (int i = 0; i < callbackHandlers.length; i++) { + CallbackHandler callbackHandler = callbackHandlers[i]; + try { + callbackHandler.handle(new Callback[]{callback}); + allUnsupported = false; + } + catch (UnsupportedCallbackException ex) { + // if an UnsupportedCallbackException occurs, go to the next handler + } + } + if (allUnsupported) { + throw new UnsupportedCallbackException(callback); + } + } + } + + private void handleCertificateValidationCallback(CertificateValidationCallback callback) { + callback.setValidator(new CertificateValidatorChain(callback)); + } + + private void handlePasswordValidationCallback(PasswordValidationCallback callback) { + callback.setValidator(new PasswordValidatorChain(callback)); + } + + private void handleTimestampValidationCallback(TimestampValidationCallback callback) { + callback.setValidator(new TimestampValidatorChain(callback)); + } + + private class TimestampValidatorChain implements TimestampValidationCallback.TimestampValidator { + + private TimestampValidationCallback callback; + + private TimestampValidatorChain(TimestampValidationCallback callback) { + this.callback = callback; + } + + public void validate(TimestampValidationCallback.Request request) + throws TimestampValidationCallback.TimestampValidationException { + for (int i = 0; i < callbackHandlers.length; i++) { + CallbackHandler callbackHandler = callbackHandlers[i]; + try { + callbackHandler.handle(new Callback[]{callback}); + callback.getResult(); + } + catch (IOException e) { + throw new TimestampValidationCallback.TimestampValidationException(e); + } + catch (UnsupportedCallbackException e) { + // ignore + } + } + } + } + + private class PasswordValidatorChain implements PasswordValidationCallback.PasswordValidator { + + private PasswordValidationCallback callback; + + private PasswordValidatorChain(PasswordValidationCallback callback) { + this.callback = callback; + } + + public boolean validate(PasswordValidationCallback.Request request) + throws PasswordValidationCallback.PasswordValidationException { + boolean allUnsupported = true; + for (int i = 0; i < callbackHandlers.length; i++) { + CallbackHandler callbackHandler = callbackHandlers[i]; + try { + callbackHandler.handle(new Callback[]{callback}); + allUnsupported = false; + if (!callback.getResult()) { + return false; + } + } + catch (IOException e) { + throw new PasswordValidationCallback.PasswordValidationException(e); + } + catch (UnsupportedCallbackException e) { + // ignore + } + } + return !allUnsupported; + } + } + + private class CertificateValidatorChain implements CertificateValidationCallback.CertificateValidator { + + private CertificateValidationCallback callback; + + private CertificateValidatorChain(CertificateValidationCallback callback) { + this.callback = callback; + } + + public boolean validate(X509Certificate certificate) + throws CertificateValidationCallback.CertificateValidationException { + boolean allUnsupported = true; + for (int i = 0; i < callbackHandlers.length; i++) { + CallbackHandler callbackHandler = callbackHandlers[i]; + try { + callbackHandler.handle(new Callback[]{callback}); + allUnsupported = false; + if (!callback.getResult()) { + return false; + } + } + catch (IOException e) { + throw new CertificateValidationCallback.CertificateValidationException(e); + } + catch (UnsupportedCallbackException e) { + // ignore + } + } + return !allUnsupported; + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/support/KeyStoreFactoryBean.java b/security/src/main/java/org/springframework/ws/soap/security/support/KeyStoreFactoryBean.java index 982934e6..f452206a 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/support/KeyStoreFactoryBean.java +++ b/security/src/main/java/org/springframework/ws/soap/security/support/KeyStoreFactoryBean.java @@ -23,19 +23,21 @@ import java.security.KeyStore; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; /** - * Spring factory bean for a java.security.KeyStore. + * Spring factory bean for a {@link KeyStore}. *

* To load an existing key store, you must set the location property. If this property is not set, a new, * empty key store is created, which is most likely not what you want. * * @author Arjen Poutsma * @see #setLocation(org.springframework.core.io.Resource) + * @see KeyStore * @since 1.0.0 */ public class KeyStoreFactoryBean implements FactoryBean, InitializingBean { diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/AbstractWss4jInterceptor.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/AbstractWss4jInterceptor.java new file mode 100644 index 00000000..b52e25ad --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/AbstractWss4jInterceptor.java @@ -0,0 +1,98 @@ +/* + * Copyright 2008 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j; + +import java.util.Vector; +import javax.xml.namespace.QName; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ws.security.WSSecurityException; +import org.apache.ws.security.util.WSSecurityUtil; +import org.w3c.dom.Document; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.saaj.SaajSoapMessage; + +/** + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @since 1.5.0 + */ +public abstract class AbstractWss4jInterceptor implements EndpointInterceptor, InitializingBean { + + protected final Log logger = LogFactory.getLog(getClass()); + + protected static final QName WS_SECURITY_NAME = + new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security"); + + private Vector actions; + + private int actionFlags; + + private String actor; + + protected Vector getActions() { + return actions; + } + + protected int getActionFlags() { + return actionFlags; + } + + public final void setActions(String actions) throws WSSecurityException { + this.actions = new Vector(); + this.actionFlags = WSSecurityUtil.decodeAction(actions, this.actions); + } + + protected String getActor() { + return actor; + } + + public void setActor(String actor) { + this.actor = actor; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(actions, "'actions' must not be null"); + } + + /** + * Transforms a soap message to a DOM document. + * + * @param soapMessage the message to transform + * @return a DOM document representing the message + */ + protected Document toDocument(SoapMessage soapMessage) { + if (soapMessage instanceof SaajSoapMessage) { + SaajSoapMessage saajMessage = (SaajSoapMessage) soapMessage; + return saajMessage.getSaajMessage().getSOAPPart(); + } + else if (soapMessage instanceof AxiomSoapMessage) { + AxiomSoapMessage axiomMessage = (AxiomSoapMessage) soapMessage; + return AxiomUtils.toDocument(axiomMessage.getAxiomMessage().getSOAPEnvelope()); + } + else { + throw new IllegalArgumentException("Unknown SoapMessage implementation [" + soapMessage + "]"); + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jHandler.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jHandler.java new file mode 100644 index 00000000..6872015e --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jHandler.java @@ -0,0 +1,172 @@ +/* + * Copyright 2008 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.Properties; +import java.util.Vector; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.ws.security.WSConstants; +import org.apache.ws.security.WSPasswordCallback; +import org.apache.ws.security.WSSecurityException; +import org.apache.ws.security.components.crypto.Crypto; +import org.apache.ws.security.handler.RequestData; +import org.apache.ws.security.handler.WSHandler; +import org.apache.ws.security.handler.WSHandlerConstants; +import org.apache.ws.security.message.token.Timestamp; +import org.w3c.dom.Document; + +import org.springframework.ws.context.MessageContext; + +/** + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @since 1.5.0 + */ +class Wss4jHandler extends WSHandler { + + /** Keys are constants from {@link WSHandlerConstants}; values are strings. */ + private Properties options = new Properties(); + + private CallbackHandler securementCallbackHandler; + + private String securementPassword; + + private Crypto securementEncryptionCrypto; + + private Crypto securementSignatureCrypto; + + public Wss4jHandler() { + // set up default handler properties + options.setProperty(WSHandlerConstants.MUST_UNDERSTAND, Boolean.toString(true)); + options.setProperty(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION, Boolean.toString(true)); + } + + protected boolean checkReceiverResults(Vector wsResult, Vector actions) { + return super.checkReceiverResults(wsResult, actions); + } + + void setOption(String key, String value) { + options.setProperty(key, value); + } + + void setOption(String key, boolean value) { + options.setProperty(key, Boolean.toString(value)); + } + + public Object getOption(String key) { + return options.getProperty(key); + } + + void setSecurementCallbackHandler(CallbackHandler securementCallbackHandler) { + this.securementCallbackHandler = securementCallbackHandler; + } + + void setSecurementPassword(String securementPassword) { + this.securementPassword = securementPassword; + } + + void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) { + this.securementEncryptionCrypto = securementEncryptionCrypto; + } + + void setSecurementSignatureCrypto(Crypto securementSignatureCrypto) { + this.securementSignatureCrypto = securementSignatureCrypto; + } + + /** Gets the password first from securementCallbackHandler, then from securementPassword if not found. */ + public WSPasswordCallback getPassword(String username, + int doAction, + String clsProp, + String refProp, + RequestData reqData) { + WSPasswordCallback callback; + if (securementCallbackHandler != null) { + int reason = 0; + + switch (doAction) { + case WSConstants.UT: + case WSConstants.UT_SIGN: + reason = WSPasswordCallback.USERNAME_TOKEN; + break; + case WSConstants.SIGN: + reason = WSPasswordCallback.SIGNATURE; + break; + case WSConstants.ENCR: + reason = WSPasswordCallback.KEY_NAME; + break; + } + callback = new WSPasswordCallback(username, reason); + Callback[] callbacks = new Callback[]{callback}; + try { + securementCallbackHandler.handle(callbacks); + } + catch (UnsupportedCallbackException ex) { + throw new Wss4jSecuritySecurementException(ex.getMessage(), ex); + } + catch (IOException ex) { + throw new Wss4jSecuritySecurementException(ex.getMessage(), ex); + } + } + else { + callback = new WSPasswordCallback("", WSPasswordCallback.UNKNOWN); + callback.setPassword(securementPassword); + } + return callback; + } + + public String getPassword(Object msgContext) { + return securementPassword; + } + + public Object getProperty(Object msgContext, String key) { + return ((MessageContext) msgContext).getProperty(key); + } + + protected Crypto loadEncryptionCrypto(RequestData reqData) throws WSSecurityException { + return securementEncryptionCrypto; + } + + public Crypto loadSignatureCrypto(RequestData reqData) throws WSSecurityException { + return securementSignatureCrypto; + } + + public void setPassword(Object msgContext, String password) { + securementPassword = password; + } + + public void setProperty(Object msgContext, String key, Object value) { + ((MessageContext) msgContext).setProperty(key, value); + } + + protected void doSenderAction(int doAction, Document doc, RequestData reqData, Vector actions, boolean isRequest) + throws WSSecurityException { + super.doSenderAction(doAction, doc, reqData, actions, isRequest); + } + + protected boolean verifyTimestamp(Timestamp timestamp, int timeToLive) throws WSSecurityException { + return super.verifyTimestamp(timestamp, timeToLive); + } + + protected boolean verifyTrust(X509Certificate cert, RequestData reqData) throws WSSecurityException { + return super.verifyTrust(cert, reqData); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityFaultException.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityFaultException.java new file mode 100755 index 00000000..59172bea --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityFaultException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j; + +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.security.WsSecurityFaultException; + +/** + * WSS4J-specific version of the {@link WsSecurityFaultException}. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class Wss4jSecurityFaultException extends WsSecurityFaultException { + + public Wss4jSecurityFaultException(QName faultCode, String faultString, String faultActor) { + super(faultCode, faultString, faultActor); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityInterceptor.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityInterceptor.java new file mode 100755 index 00000000..84273068 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityInterceptor.java @@ -0,0 +1,578 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j; + +import java.security.cert.X509Certificate; +import java.util.Vector; +import javax.security.auth.callback.CallbackHandler; + +import org.apache.axiom.soap.SOAPEnvelope; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPMessage; +import org.apache.ws.security.WSConstants; +import org.apache.ws.security.WSSecurityEngine; +import org.apache.ws.security.WSSecurityEngineResult; +import org.apache.ws.security.WSSecurityException; +import org.apache.ws.security.components.crypto.Crypto; +import org.apache.ws.security.handler.RequestData; +import org.apache.ws.security.handler.WSHandlerConstants; +import org.apache.ws.security.handler.WSHandlerResult; +import org.apache.ws.security.message.token.Timestamp; +import org.apache.ws.security.util.WSSecurityUtil; +import org.w3c.dom.Document; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.security.AbstractWsSecurityInterceptor; +import org.springframework.ws.soap.security.WsSecuritySecurementException; +import org.springframework.ws.soap.security.WsSecurityValidationException; + +/** + * A WS-Security endpoint interceptor based on Apache Wss4j. The inteceptor supports both Axiom and Saaj messages. The + * interceptor's configuration does not rely on an external configuration files and thus is set using the various + * attributes. + *

+ * The actions executed by the interceptor are configured via validationActions and + * securementActions attributes. Actions are passed as a space separated string. + *

+ * Validation actions are:

The order of the actions that the client performed to secure the messages is significant and is + * enforced by the interceptor. + *

+ * Securement actions are:

+ * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor implements InitializingBean { + + public static final String SECUREMENT_USER_PROPERTY_NAME = "Wss4jSecurityInterceptor.securementUser"; + + private CallbackHandler validationCallbackHandler; + + private int securementAction; + + private String securementActions; + + private Vector securementActionsVector; + + private String securementUsername; + + private boolean timestampStrict = true; + + private int timeToLive = 300; + + private int validationAction; + + private String validationActions; + + private Vector validationActionsVector; + + private String validationActor; + + private Crypto validationDecryptionCrypto; + + private Crypto validationSignatureCrypto; + + private Wss4jHandler handler = new Wss4jHandler(); + + private boolean enableSignatureConfirmation; + + public void setSecurementActions(String securementActions) { + this.securementActions = securementActions; + securementActionsVector = new Vector(); + try { + securementAction = WSSecurityUtil.decodeAction(securementActions, securementActionsVector); + } + catch (WSSecurityException ex) { + throw new IllegalArgumentException(ex); + } + } + + /** + * The actor name of the wsse:Security header. + *

+ * If this parameter is omitted, the actor name is not set. + *

+ * The value of the actor or role has to match the receiver's setting or may contain standard values. + */ + public void setSecurementActor(String securementActor) { + handler.setOption(WSHandlerConstants.ACTOR, securementActor); + } + + public void setSecurementCallbackHandler(CallbackHandler securementCallbackHandler) { + handler.setSecurementCallbackHandler(securementCallbackHandler); + } + + public void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) { + handler.setSecurementEncryptionCrypto(securementEncryptionCrypto); + } + + /** + * Defines which key identifier type to use. The WS-Security specifications recommends to use the identifier type + * IssuerSerial. For possible encryption key identifier types refer to {@link + * org.apache.ws.security.handler.WSHandlerConstants#keyIdentifier}. For encryption IssuerSerial, + * X509KeyIdentifier, DirectReference, Thumbprint, + * SKIKeyIdentifier, and EmbeddedKeyName are valid only. + */ + public void setSecurementEncryptionKeyIdentifier(String securementEncryptionKeyIdentifier) { + handler.setOption(WSHandlerConstants.ENC_KEY_ID, securementEncryptionKeyIdentifier); + } + + /** + * Defines which algorithm to use to encrypt the generated symmetric key. Currently WSS4J supports {@link + * WSConstants#KEYTRANSPORT_RSA15} only. + */ + public void setSecurementEncryptionKeyTransportAlgorithm(String securementEncryptionKeyTransportAlgorithm) { + handler.setOption(WSHandlerConstants.ENC_KEY_TRANSPORT, securementEncryptionKeyTransportAlgorithm); + } + + /** + * Parameter to define which parts of the request shall be encrypted. + *

+ * The value of this parameter is a list of semi-colon separated element names that identify the elements to + * encrypt. An encryption mode specifier and a namespace identification, each inside a pair of curly brackets, may + * preceed each element name. + *

+ * The encryption mode specifier is either {Content} or {Element}. Please refer to the W3C + * XML Encryption specification about the differences between Element and Content encryption. The encryption mode + * defaults to Content if it is omitted. Example of a list: + *

+     * <parameter name="encryptionParts"
+     *   value="{Content}{http://example.org/paymentv2}CreditCard;
+     *             {Element}{}UserName" />
+     * 
+ * The the first entry of the list identifies the element CreditCard in the namespace + * http://example.org/paymentv2, and will encrypt its content. Be aware that the element name, the + * namespace identifier, and the encryption modifier are case sensitive. + *

+ * The encryption modifier and the namespace identifier can be ommited. In this case the encryption mode defaults to + * Content and the namespace is set to the SOAP namespace. + *

+ * An empty encryption mode defaults to Content, an empty namespace identifier defaults to the SOAP + * namespace. The second line of the example defines Element as encryption mode for an + * UserName element in the SOAP namespace. + *

+ * To specify an element without a namespace use the string Null as the namespace name (this is a case + * sensitive string) + *

+ * If no list is specified, the handler encrypts the SOAP Body in Content mode by default. + */ + public void setSecurementEncryptionParts(String securementEncryptionParts) { + handler.setOption(WSHandlerConstants.ENCRYPTION_PARTS, securementEncryptionParts); + } + + /** + * Defines which symmetric encryption algorithm to use. WSS4J supports the following alorithms: {@link + * WSConstants#TRIPLE_DES}, {@link WSConstants#AES_128}, {@link WSConstants#AES_256}, and {@link + * WSConstants#AES_192}. Except for AES 192 all of these algorithms are required by the XML Encryption + * specification. + */ + public void setSecurementEncryptionSymAlgorithm(String securementEncryptionSymAlgorithm) { + this.handler.setOption(WSHandlerConstants.ENC_SYM_ALGO, securementEncryptionSymAlgorithm); + } + + /** + * The user's name for encryption. + *

+ * The encryption functions uses the public key of this user's certificate to encrypt the generated symmetric key. + *

+ * If this parameter is not set, then the encryption function falls back to the {@link + * org.apache.ws.security.handler.WSHandlerConstants#USER} parameter to get the certificate. + *

+ * If only encryption of the SOAP body data is requested, it is recommended to use this parameter to define + * the username. The application can then use the standard user and password functions (see example at {@link + * org.apache.ws.security.handler.WSHandlerConstants#USER} to enable HTTP authentication functions. + *

+ * Encryption only does not authenticate a user / sender, therefore it does not need a password. + *

+ * Placing the username of the encryption certficate in the WSDD is not a security risk, because the public key of + * that certificate is used only. + *

+ * The application may set this parameter using the following method: + *

+     * call.setProperty(WSHandlerConstants.ENCYRPTION_USER, "encryptionuser");
+     * 
+ * However, the parameter in the WSDD deployment file overwrites the property setting (deployment setting overwrites + * application setting). + */ + public void setSecurementEncryptionUser(String securementEncryptionUser) { + handler.setOption(WSHandlerConstants.ENCRYPTION_USER, securementEncryptionUser); + } + + public void setSecurementPassword(String securementPassword) { + this.handler.setSecurementPassword(securementPassword); + } + + /** + * Specific parameter for UsernameToken action to define the encoding of the passowrd. + *

+ * The parameter can be set to either {@link WSConstants#PW_DIGEST} or to {@link WSConstants#PW_TEXT}. + *

+ * The default setting is PW_DIGEST. + */ + public void setSecurementPasswordType(String securementUsernameTokenPasswordType) { + handler.setOption(WSHandlerConstants.PASSWORD_TYPE, securementUsernameTokenPasswordType); + } + + /** + * Defines which signature algorithm to use. Currently this parameter is ignored - SHA1RSA is the only supported + * algorithm, will be enhanced soon. + */ + public void setSecurementSignatureAlgorithm(String securementSignatureAlgorithm) { + handler.setOption(WSHandlerConstants.SIG_ALGO, securementSignatureAlgorithm); + } + + public void setSecurementSignatureCrypto(Crypto securementSignatureCrypto) { + handler.setSecurementSignatureCrypto(securementSignatureCrypto); + } + + /** + * Defines which key identifier type to use. The WS-Security specifications recommends to use the identifier type + * IssuerSerial. For possible signature key identifier types refer to {@link + * org.apache.ws.security.handler.WSHandlerConstants#keyIdentifier}. For signature IssuerSerial and + * DirectReference are valid only. + */ + public void setSecurementSignatureKeyIdentifier(String securementSignatureKeyIdentifier) { + handler.setOption(WSHandlerConstants.SIG_KEY_ID, securementSignatureKeyIdentifier); + } + + /** + * Parameter to define which parts of the request shall be signed. + *

+ * Refer to {@link #setSecurementEncryptionParts(String)} for a detailed description of the format of the value + * string. + *

+ * If this parameter is not specified the handler signs the SOAP Body by default. + *

+ * The WS Security specifications define several formats to transfer the signature tokens (certificates) or + * references to these tokens. Thus, the plain element name Token signs the token and takes care of the + * different format. + *

+ * To sign the SOAP body and the signature token the value of this parameter must contain: + *

+     * <parameter name="signatureParts"
+     *   value="{}{http://schemas.xmlsoap.org/soap/envelope/}Body; Token" />
+     * 
+ * To specify an element without a namespace use the string Null as the namespace name (this is a case + * sensitive string) + *

+ * If there is no other element in the request with a local name of Body then the SOAP namespace + * identifier can be empty ({}). + */ + public void setSecurementSignatureParts(String securementSignatureParts) { + handler.setOption(WSHandlerConstants.SIGNATURE_PARTS, securementSignatureParts); + } + + public void setSecurementUsername(String securementUsername) { + this.securementUsername = securementUsername; + } + + /** Sets the server-side time to live */ + public void setTimeToLive(int timeToLive) { + if (timeToLive <= 0) { + throw new IllegalArgumentException("timeToLive must be positive"); + } + this.timeToLive = timeToLive; + } + + /** Sets the validation actions to be executed by the interceptor. */ + public void setValidationActions(String actions) { + this.validationActions = actions; + try { + validationActionsVector = new Vector(); + validationAction = WSSecurityUtil.decodeAction(actions, validationActionsVector); + } + catch (WSSecurityException ex) { + throw new IllegalArgumentException(ex); + } + } + + public void setValidationActor(String validationActor) { + this.validationActor = validationActor; + } + + public void setValidationCallbackHandler(CallbackHandler callbackHandler) { + this.validationCallbackHandler = callbackHandler; + } + + /** Sets the Crypto to use to decrypt incoming messages */ + public void setValidationDecryptionCrypto(Crypto decryptionCrypto) { + this.validationDecryptionCrypto = decryptionCrypto; + } + + /** Sets the Crypto to use to verify the signature of incoming messages */ + public void setValidationSignatureCrypto(Crypto signatureCrypto) { + this.validationSignatureCrypto = signatureCrypto; + } + + /** Whether to enable signatureConfirmation or not. By default signatureConfirmation is enabled */ + public void setEnableSignatureConfirmation(boolean enableSignatureConfirmation) { + handler.setOption(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION, enableSignatureConfirmation); + this.enableSignatureConfirmation = enableSignatureConfirmation; + } + + /** Sets if the generated timestamp header's precision is in milliseconds. */ + public void setTimestampPrecisionInMilliseconds(boolean timestampPrecisionInMilliseconds) { + handler.setOption(WSHandlerConstants.TIMESTAMP_PRECISION, timestampPrecisionInMilliseconds); + } + + /** Sets whether or not timestamp verification is done with the server-side time to live */ + public void setTimestampStrict(boolean timestampStrict) { + this.timestampStrict = timestampStrict; + } + + /** + * Enables the mustUnderstand attribute on WS-Security headers on outgoing messages. Default is + * true. + */ + public void setSecurementMustUnderstand(boolean securementMustUnderstand) { + handler.setOption(WSHandlerConstants.MUST_UNDERSTAND, securementMustUnderstand); + } + + /** + * Sets the additional elements in UsernameTokens. + *

+ * The value of this parameter is a list of element names that are added to the UsernameToken. The names of the list + * a separated by spaces. + *

+ * The list may containe the names nonce and created only. Use this option if the password + * type is passwordText and the handler shall add the Nonce and/or Created + * elements. + */ + public void setSecurementUsernameTokenElements(String securementUsernameTokenElements) { + handler.setOption(WSHandlerConstants.ADD_UT_ELEMENTS, securementUsernameTokenElements); + } + + public void afterPropertiesSet() throws Exception { + Assert.isTrue(validationActions != null || securementActions != null, + "validationActions or securementActions are required"); + if (validationActions != null) { + if ((validationAction & WSConstants.UT) != 0) { + Assert.notNull(validationCallbackHandler, "validationCallbackHandler is required"); + } + + if ((validationAction & WSConstants.SIGN) != 0) { + Assert.notNull(validationSignatureCrypto, "validationSignatureCrypto is required"); + } + + if ((validationAction & WSConstants.ENCR) != 0) { + Assert.notNull(validationDecryptionCrypto, "validationDecryptionCrypto is required"); + } + } + // securement actions are not to be validated at start up as they could + // be configured dynamically via the message context + } + + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecuritySecurementException { + if (securementAction == WSConstants.NO_SECURITY && !enableSignatureConfirmation) { + return; + } + RequestData requestData = initializeRequestData(messageContext); + + Document envelopeAsDocument = getEnvelopeAsDocument(soapMessage); + try { + // In case on signature confirmation with no other securement + // action, we need to pass an empty securementActionsVector to avoid + // NPE + if (securementAction == WSConstants.NO_SECURITY) { + securementActionsVector = new Vector(0); + } + + handler.doSenderAction(securementAction, envelopeAsDocument, requestData, securementActionsVector, false); + } + catch (WSSecurityException ex) { + throw new Wss4jSecuritySecurementException(ex.getMessage(), ex); + } + + replaceMessageIfNecessary(soapMessage, envelopeAsDocument); + } + + /** Creates and initializes a request data */ + private RequestData initializeRequestData(MessageContext messageContext) { + RequestData requestData = new RequestData(); + requestData.setMsgContext(messageContext); + + // reads securementUsername first from the context then from the + // property + String su = (String) messageContext + .getProperty(SECUREMENT_USER_PROPERTY_NAME); + if (su != null && !su.equals("")) { + requestData.setUsername(su); + } + else { + requestData.setUsername(securementUsername); + + } + + requestData.setUsername(securementUsername); + return requestData; + } + + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { + if (logger.isDebugEnabled()) { + logger.debug("validating message: " + soapMessage + " with actions: " + validationActions); + } + + if (validationAction == WSConstants.NO_SECURITY) { + return; + } + + Document envelopeAsDocument = getEnvelopeAsDocument(soapMessage); + + // Header processing + WSSecurityEngine securityEngine = WSSecurityEngine.getInstance(); + + try { + Vector results = securityEngine.processSecurityHeader(envelopeAsDocument, validationActor, + validationCallbackHandler, validationSignatureCrypto, validationDecryptionCrypto); + + // Results verification + if (results == null) { + throw new Wss4jSecurityValidationException("No WS-Security header found"); + } + + if (!handler.checkReceiverResults(results, validationActionsVector)) { + throw new Wss4jSecurityValidationException("Security processing failed (actions mismatch)"); + } + + // puts the results in the context + // useful for Signature Confirmation + updateContextWithResults(messageContext, results); + + verifyCertificateTrust(results); + + verifyTimestamp(results); + } + catch (WSSecurityException ex) { + throw new Wss4jSecurityValidationException(ex.getMessage(), ex); + } + + replaceMessageIfNecessary(soapMessage, envelopeAsDocument); + + soapMessage.getEnvelope().getHeader().removeHeaderElement(WS_SECURITY_NAME); + } + + /** + * Transforms a soap message to a DOM document. + * + * @param soapMessage the message to transform + * @return a DOM document representing the message + */ + private Document getEnvelopeAsDocument(SoapMessage soapMessage) { + if (soapMessage instanceof SaajSoapMessage) { + SaajSoapMessage saajMessage = (SaajSoapMessage) soapMessage; + return saajMessage.getSaajMessage().getSOAPPart(); + } + + if (soapMessage instanceof AxiomSoapMessage) { + AxiomSoapMessage axiomMessage = (AxiomSoapMessage) soapMessage; + return AxiomUtils.toDocument(axiomMessage.getAxiomMessage().getSOAPEnvelope()); + } + + throw new UnsupportedOperationException("Message type not supported: " + soapMessage); + } + + /** + * Puts the results of WS-Security headers processing in the message context. Some actions like Signature + * Confirmation + */ + private void updateContextWithResults(MessageContext messageContext, Vector results) { + Vector handlerResults; + if ((handlerResults = (Vector) messageContext + .getProperty(WSHandlerConstants.RECV_RESULTS)) == null) { + handlerResults = new Vector(); + messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults); + } + WSHandlerResult rResult = new WSHandlerResult(validationActor, results); + handlerResults.add(0, rResult); + messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults); + } + + /** + * + * @param results + * @throws WSSecurityException + */ + protected void verifyCertificateTrust(Vector results) throws WSSecurityException { + RequestData requestData = new RequestData(); + requestData.setSigCrypto(validationSignatureCrypto); + WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.SIGN); + + if (actionResult != null) { + X509Certificate returnCert = actionResult.getCertificate(); + if (!handler.verifyTrust(returnCert, requestData)) { + throw new Wss4jSecurityValidationException("The certificate used for the signature is not trusted"); + } + } + } + + /** + * + * @param results + * @throws WSSecurityException + */ + protected void verifyTimestamp(Vector results) throws WSSecurityException { + WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.TS); + + if (actionResult != null) { + Timestamp timestamp = actionResult.getTimestamp(); + + if (timestamp != null && timestampStrict) { + if (!handler.verifyTimestamp(timestamp, timeToLive)) { + throw new Wss4jSecurityValidationException("Invalid timestamp : " + timestamp.getID()); + } + } + } + + } + + /** + * Replaces an axiom message. + * + * @param soapMessage the soap message to replace + * @param envelope the new envelope + */ + private void replaceMessageIfNecessary(SoapMessage soapMessage, Document envelope) { + if (soapMessage instanceof AxiomSoapMessage) { + // construct a new Axiom message with the processed envelope + AxiomSoapMessage axiomMessage = (AxiomSoapMessage) soapMessage; + SOAPEnvelope envelopeFromDOMDocument = AxiomUtils.toEnvelope(envelope); + SOAPFactory factory = (SOAPFactory) axiomMessage.getAxiomMessage().getOMFactory(); + SOAPMessage newMessage = factory.createSOAPMessage(); + newMessage.setSOAPEnvelope(envelopeFromDOMDocument); + + // replace the Axiom message + axiomMessage.setAxiomMessage(newMessage); + } + } + +} \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecuritySecurementException.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecuritySecurementException.java new file mode 100755 index 00000000..7e8fef6d --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecuritySecurementException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j; + +import org.springframework.ws.soap.security.WsSecuritySecurementException; + +/** + * WSS4J-specific version of the {@link WsSecuritySecurementException}. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class Wss4jSecuritySecurementException extends WsSecuritySecurementException { + + public Wss4jSecuritySecurementException(String msg) { + super(msg); + } + + public Wss4jSecuritySecurementException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityValidationException.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityValidationException.java new file mode 100755 index 00000000..841fa5d1 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/Wss4jSecurityValidationException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j; + +import org.springframework.ws.soap.security.WsSecurityValidationException; + +/** + * WSS4J-specific version of the {@link WsSecurityValidationException}. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class Wss4jSecurityValidationException extends WsSecurityValidationException { + + public Wss4jSecurityValidationException(String msg) { + super(msg); + } + + public Wss4jSecurityValidationException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/AbstractWss4jCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/AbstractWss4jCallbackHandler.java new file mode 100755 index 00000000..6f3869f3 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/AbstractWss4jCallbackHandler.java @@ -0,0 +1,182 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j.callback; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStore.Entry; +import javax.crypto.SecretKey; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.ws.security.WSPasswordCallback; +import org.apache.ws.security.WSSecurityException; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; + +/** + * A base class for callback handlers. + * + * @author Tareq Abed Rabbo + */ +public abstract class AbstractWss4jCallbackHandler extends AbstractCallbackHandler implements InitializingBean { + + private boolean passwordDigestRequired; + + private boolean passwordPlainTextRequired; + + private String keyPassword; + + private KeyStore keyStore; + + /** Sets the key store to use if a symmetric key name is embedded. */ + public void setKeyStore(KeyStore keyStore) { + this.keyStore = keyStore; + } + + /** Sets if a digest password is required. */ + public void setPasswordDigestRequired(boolean passwordDigestRequired) { + this.passwordDigestRequired = passwordDigestRequired; + } + + /** Sets the password of the key used for decryption. */ + public void setKeyPassword(String keyPassword) { + this.keyPassword = keyPassword; + } + + /** Sets if a plain text password is required. */ + public void setPasswordPlainTextRequired(boolean passwordPlainTextRequired) { + this.passwordPlainTextRequired = passwordPlainTextRequired; + } + + /** Returns the password of the key used for decryption. */ + public String getKeyPassword() { + return keyPassword; + } + + /** Returns if a digest password is required. */ + public boolean isPasswordDigestRequired() { + return passwordDigestRequired; + } + + /** Returns if a plain text password is required. */ + public boolean isPasswordPlainTextRequired() { + return passwordPlainTextRequired; + } + + /** Gets the key store to use if a symmetric key name is embedded. */ + public KeyStore getKeyStore() { + return keyStore; + } + + public void afterPropertiesSet() throws Exception { + Assert + .isTrue(!(passwordDigestRequired && passwordPlainTextRequired), + "passwordDigestRequired and passwordPlainTextRequired can not be true in the same time"); + } + + protected final void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + + if (callback instanceof WSPasswordCallback) { + WSPasswordCallback passwordCallback = (WSPasswordCallback) callback; + + int usage = passwordCallback.getUsage(); + + if (passwordDigestRequired && !(usage == WSPasswordCallback.USERNAME_TOKEN)) { + throw new WSSecurityException("digest password required"); + } + + if (passwordPlainTextRequired && !(usage == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN)) { + throw new WSSecurityException("plain text password required"); + } + + String id = passwordCallback.getIdentifer(); + switch (usage) { + + // plain text password + case WSPasswordCallback.USERNAME_TOKEN_UNKNOWN: + validateUsernameTokenPlainText(passwordCallback); + return; + + // digest password + case WSPasswordCallback.USERNAME_TOKEN: + validateUsernameTokenDigest(passwordCallback); + return; + + // decryption + case WSPasswordCallback.DECRYPT: + passwordCallback.setPassword(getDecryptionKeyPassword(id)); + return; + + // decryption with an embedded symmetric key name + case WSPasswordCallback.KEY_NAME: + try { + KeyStore.PasswordProtection protection = + new KeyStore.PasswordProtection(getSymmetricKeyPassword(id).toCharArray()); + Entry entry = keyStore.getEntry(id, protection); + if (entry instanceof KeyStore.SecretKeyEntry) { + KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) entry; + SecretKey secretKey = secretKeyEntry.getSecretKey(); + passwordCallback.setKey(secretKey.getEncoded()); + } + else { + throw new RuntimeException("key must be instance of javax.crypto.SecretKey:" + id); + } + } + catch (GeneralSecurityException ex) { + throw new Wss4jSecurityCallbackHandlerException(ex + .getMessage(), ex); + } + return; + default: + throw new UnsupportedOperationException("usage type not suporrted:" + usage); + } + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + protected String getDecryptionKeyPassword(String id) { + return keyPassword; + } + + protected String getSymmetricKeyPassword(String id) { + return keyPassword; + } + + /** + * validates a Username token with a plain text password. The implementation must validate the username and the + * password and must throw an exception if the token is not valid + * + * @param callback the callback created by Wss4j + * @throws WSSecurityException if the token is not valid + */ + abstract protected void validateUsernameTokenPlainText(WSPasswordCallback callback) throws WSSecurityException; + + /** + * validates a Username token with a digest password. The implementation must fetch the clear password of the and + * set the password attribute of the callback. Wss4j performs the validation logic. + * + * @param callback the callback created by Wss4j + * @throws WSSecurityException if the token is not valid + */ + abstract protected void validateUsernameTokenDigest(WSPasswordCallback callback) throws WSSecurityException; +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/SimpleCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/SimpleCallbackHandler.java new file mode 100755 index 00000000..f9c5e63b --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/SimpleCallbackHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j.callback; + +import java.util.Properties; + +import org.apache.ws.security.WSPasswordCallback; +import org.apache.ws.security.WSSecurityException; + +/** @author Tareq Abed Rabbo */ +public class SimpleCallbackHandler extends AbstractWss4jCallbackHandler { + + private Properties users = new Properties(); + + public void setUsers(Properties users) { + this.users = users; + } + + public Properties getUsers() { + return users; + } + + protected void validateUsernameTokenPlainText(WSPasswordCallback callback) throws WSSecurityException { + String storedPassword = users.getProperty(callback.getIdentifer()); + if (!(storedPassword != null && storedPassword.equals(callback + .getPassword()))) { + throw new WSSecurityException(WSSecurityException.FAILURE); + } + } + + protected void validateUsernameTokenDigest(WSPasswordCallback callback) throws WSSecurityException { + callback.setPassword(users.getProperty((callback.getIdentifer()))); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/Wss4jSecurityCallbackHandlerException.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/Wss4jSecurityCallbackHandlerException.java new file mode 100755 index 00000000..a7b2cc72 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/Wss4jSecurityCallbackHandlerException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j.callback; + +import org.springframework.ws.soap.security.WsSecurityException; + +public class Wss4jSecurityCallbackHandlerException extends WsSecurityException { + + public Wss4jSecurityCallbackHandlerException(String msg, Throwable ex) { + super(msg, ex); + } + + public Wss4jSecurityCallbackHandlerException(String msg) { + super(msg); + } + +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiCallbackHandler.java new file mode 100755 index 00000000..d378bc8e --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiCallbackHandler.java @@ -0,0 +1,138 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j.callback.acegi; + +import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationException; +import org.acegisecurity.AuthenticationManager; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.acegisecurity.providers.dao.UserCache; +import org.acegisecurity.providers.dao.cache.NullUserCache; +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UserDetailsService; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.apache.ws.security.WSPasswordCallback; +import org.apache.ws.security.WSSecurityException; + +import org.springframework.dao.DataAccessException; +import org.springframework.util.Assert; +import org.springframework.ws.soap.security.wss4j.callback.AbstractWss4jCallbackHandler; + +/** @author Tareq Abed Rabbo */ +public class AcegiCallbackHandler extends AbstractWss4jCallbackHandler { + + private AuthenticationManager authenticationManager; + + private UserCache userCache = new NullUserCache(); + + private UserDetailsService userDetailsService; + + public UserCache getUserCache() { + return userCache; + } + + public void setUserCache(UserCache userCache) { + this.userCache = userCache; + } + + public UserDetailsService getUserDetailsService() { + return userDetailsService; + } + + public void setUserDetailsService(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + public AuthenticationManager getAuthenticationManager() { + return authenticationManager; + } + + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + + if (isPasswordPlainTextRequired()) { + Assert.notNull(authenticationManager, "authenticationManager is required"); + } + + if (isPasswordDigestRequired()) { + Assert + .notNull(userDetailsService, "userDetailsService is required"); + } + + } + + protected void validateUsernameTokenPlainText(WSPasswordCallback callback) throws WSSecurityException { + if (isPasswordPlainTextRequired()) { + Assert + .notNull(authenticationManager, + "authenticationManager is required to validate a usernameToken with a plain text password"); + } + try { + Authentication authResult = authenticationManager + .authenticate( + new UsernamePasswordAuthenticationToken(callback.getIdentifer(), callback.getPassword())); + if (logger.isDebugEnabled()) { + logger + .debug("Authentication success: " + authResult.toString()); + } + SecurityContextHolder.getContext().setAuthentication(authResult); + } + catch (AuthenticationException failed) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for user '" + callback.getIdentifer() + "' failed: " + + failed.toString()); + } + SecurityContextHolder.getContext().setAuthentication(null); + throw new WSSecurityException(WSSecurityException.FAILURE); + } + } + + protected void validateUsernameTokenDigest(WSPasswordCallback callback) throws WSSecurityException { + if (isPasswordDigestRequired()) { + Assert + .notNull(userDetailsService, + "userDetailsService is required to validate a usernameToken with a digest password"); + } + UserDetails user = loadUserDetails(callback.getIdentifer()); + if (user != null) { + callback.setPassword(user.getPassword()); + } + } + + private UserDetails loadUserDetails(String username) throws DataAccessException { + UserDetails user = userCache.getUserFromCache(username); + + if (user == null) { + try { + user = userDetailsService.loadUserByUsername(username); + } + catch (UsernameNotFoundException notFound) { + if (logger.isDebugEnabled()) { + logger.debug("Username '" + username + "' not found"); + } + return null; + } + userCache.putUserInCache(user); + } + return user; + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiSecurityContextUpdateAdvice.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiSecurityContextUpdateAdvice.java new file mode 100755 index 00000000..0e06f1b5 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiSecurityContextUpdateAdvice.java @@ -0,0 +1,80 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j.callback.acegi; + +import java.lang.reflect.Method; +import java.util.Vector; + +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.apache.ws.security.WSConstants; +import org.apache.ws.security.WSSecurityEngineResult; +import org.apache.ws.security.WSUsernameTokenPrincipal; +import org.apache.ws.security.handler.WSHandlerConstants; +import org.apache.ws.security.handler.WSHandlerResult; +import org.apache.ws.security.util.WSSecurityUtil; + +import org.springframework.aop.AfterReturningAdvice; +import org.springframework.aop.ThrowsAdvice; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor; + +/** + * This class is responsible for setting Acegi's security context after the request is validated. It must be used in + * conjunction with AcegiCallbackHandler when validating a username token with a digest password. + * + * @author tareq.abedrabbo + */ +public class AcegiSecurityContextUpdateAdvice implements AfterReturningAdvice, ThrowsAdvice { + + public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { + + if (!(Wss4jSecurityInterceptor.class + .isAssignableFrom(target.getClass()))) { + throw new IllegalArgumentException( + "AcegiSecurityContextUpdateAdvice can only be applied to a Wss4jSecurityInterceptor"); + } + + MessageContext context = (MessageContext) args[0]; + + Vector wsHandlerResults = (Vector) context + .getProperty(WSHandlerConstants.RECV_RESULTS); + + if (wsHandlerResults != null) { + WSHandlerResult handlerResult = (WSHandlerResult) wsHandlerResults + .get(0); + Vector results = handlerResult.getResults(); + WSSecurityEngineResult actionResult = WSSecurityUtil + .fetchActionResult(results, WSConstants.UT); + if (actionResult != null) { + WSUsernameTokenPrincipal principal = (WSUsernameTokenPrincipal) actionResult + .getPrincipal(); + if (principal.getPasswordType().equals(WSConstants.PASSWORD_DIGEST)) { + String user = principal.getName(); + String password = principal.getPassword(); + UsernamePasswordAuthenticationToken authRequest = + new UsernamePasswordAuthenticationToken(user, password); + SecurityContextHolder.getContext().setAuthentication(authRequest); + } + } + } + } + + public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable { + SecurityContextHolder.getContext().setAuthentication(null); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/package.html b/security/src/main/java/org/springframework/ws/soap/security/wss4j/package.html new file mode 100644 index 00000000..a6e7beca --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/package.html @@ -0,0 +1,6 @@ + + +Contains classes for using the Apache WSS4J WS-Security implementation within +Spring-WS. + + \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/support/CryptoFactoryBean.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/support/CryptoFactoryBean.java new file mode 100755 index 00000000..3991f031 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/support/CryptoFactoryBean.java @@ -0,0 +1,79 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j.support; + +import java.util.Properties; + +import org.apache.ws.security.components.crypto.Crypto; +import org.apache.ws.security.components.crypto.CryptoFactory; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * Spring factory bean for a WSS4J {@link Crypto}. + *

+ * Requires the {@link #setConfiguration(java.util.Properties) configuration} property to be set. This configuration + * should have the org.apache.ws.security.crypto.provider property defined. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @see org.apache.ws.security.components.crypto.Crypto + * @since 1.5.0 + */ +public class CryptoFactoryBean implements FactoryBean, BeanClassLoaderAware, InitializingBean { + + private Properties configuration; + + private ClassLoader classLoader; + + private Crypto crypto; + + /** + * Sets the configuration of the Crypto. + * + * @see org.apache.ws.security.components.crypto.CryptoFactory#getInstance(java.util.Properties) + */ + public void setConfiguration(Properties properties) { + this.configuration = properties; + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(configuration, "'configuration' is required"); + + this.crypto = CryptoFactory.getInstance(configuration, classLoader); + } + + public Class getObjectType() { + return Crypto.class; + } + + public boolean isSingleton() { + return true; + } + + public Object getObject() throws Exception { + return crypto; + } + +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/support/package.html b/security/src/main/java/org/springframework/ws/soap/security/wss4j/support/package.html new file mode 100644 index 00000000..e06c6873 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/support/package.html @@ -0,0 +1,5 @@ + + +Contains support classes for working with WSS4J. + + \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptor.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptor.java index dcfd27d4..24a505cf 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptor.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptor.java @@ -25,14 +25,16 @@ import com.sun.xml.wss.XWSSProcessor; import com.sun.xml.wss.XWSSProcessorFactory; import com.sun.xml.wss.XWSSecurityException; import com.sun.xml.wss.impl.WssSoapFaultException; + import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; import org.springframework.util.Assert; +import org.springframework.ws.context.MessageContext; import org.springframework.ws.soap.SoapMessage; import org.springframework.ws.soap.saaj.SaajSoapMessage; import org.springframework.ws.soap.security.AbstractWsSecurityInterceptor; import org.springframework.ws.soap.security.WsSecurityValidationException; -import org.springframework.ws.soap.security.xwss.callback.CallbackHandlerChain; +import org.springframework.ws.soap.security.callback.CallbackHandlerChain; /** * WS-Security endpoint interceptor that is based on Sun's XML and Web Services Security package (XWSS). This @@ -87,9 +89,7 @@ public class XwsSecurityInterceptor extends AbstractWsSecurityInterceptor implem this.callbackHandler = new CallbackHandlerChain(callbackHandler); } - /** - * Sets the policy configuration to use for XWSS. Required. - */ + /** Sets the policy configuration to use for XWSS. Required. */ public void setPolicyConfiguration(Resource policyConfiguration) { this.policyConfiguration = policyConfiguration; } @@ -121,7 +121,8 @@ public class XwsSecurityInterceptor extends AbstractWsSecurityInterceptor implem * @throws XwsSecuritySecurementException in case of errors * @throws IllegalArgumentException when soapMessage is not a SaajSoapMessage */ - protected void secureMessage(SoapMessage soapMessage) throws XwsSecuritySecurementException { + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws XwsSecuritySecurementException { Assert.isTrue(soapMessage instanceof SaajSoapMessage, "XwsSecurityInterceptor requires a SaajSoapMessage. " + "Use a SaajSoapMessageFactory to create the SOAP messages."); SaajSoapMessage saajSoapMessage = (SaajSoapMessage) soapMessage; @@ -145,7 +146,8 @@ public class XwsSecurityInterceptor extends AbstractWsSecurityInterceptor implem * @throws XwsSecurityValidationException in case of errors * @throws IllegalArgumentException when soapMessage is not a SaajSoapMessage */ - protected void validateMessage(SoapMessage soapMessage) throws WsSecurityValidationException { + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { Assert.isTrue(soapMessage instanceof SaajSoapMessage, "XwsSecurityInterceptor requires a SaajSoapMessage. " + "Use a SaajSoapMessageFactory to create the SOAP messages."); SaajSoapMessage saajSoapMessage = (SaajSoapMessage) soapMessage; diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CryptographyCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CryptographyCallbackHandler.java index fa627616..8bdd487d 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CryptographyCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CryptographyCallbackHandler.java @@ -26,6 +26,8 @@ import com.sun.xml.wss.impl.callback.EncryptionKeyCallback; import com.sun.xml.wss.impl.callback.SignatureKeyCallback; import com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; + /** * Default callback handler that handles cryptographic callback. This handler determines the exact callback passed, and * calls a template method for it. By default, all template methods throw an UnsupportedCallbackException, @@ -80,9 +82,9 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * implementation delegates to specific handling methods. * * @see #handlePrivateKeyRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, - *com.sun.xml.wss.impl.callback.DecryptionKeyCallback.PrivateKeyRequest) + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.PrivateKeyRequest) * @see #handleSymmetricKeyRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, - *com.sun.xml.wss.impl.callback.DecryptionKeyCallback.SymmetricKeyRequest) + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.SymmetricKeyRequest) */ protected final void handleDecryptionKeyCallback(DecryptionKeyCallback callback) throws IOException, UnsupportedCallbackException { @@ -102,13 +104,13 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * handleDecryptionKeyCallback(). Default implementation delegates to specific handling methods. * * @see #handlePublicKeyBasedPrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest) + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest) * @see #handleX509CertificateBasedRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, - *com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509CertificateBasedRequest) + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509CertificateBasedRequest) * @see #handleX509IssuerSerialBasedRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, - *com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509IssuerSerialBasedRequest) + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509IssuerSerialBasedRequest) * @see #handleX509SubjectKeyIdentifierBasedRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, - *com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509SubjectKeyIdentifierBasedRequest) + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509SubjectKeyIdentifierBasedRequest) */ protected final void handlePrivateKeyRequest(DecryptionKeyCallback callback, DecryptionKeyCallback.PrivateKeyRequest request) @@ -180,7 +182,7 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * handleDecryptionKeyCallback(). Default implementation delegates to specific handling methods. * * @see #handleAliasSymmetricKeyRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, - *com.sun.xml.wss.impl.callback.DecryptionKeyCallback.AliasSymmetricKeyRequest) + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.AliasSymmetricKeyRequest) */ protected final void handleSymmetricKeyRequest(DecryptionKeyCallback callback, DecryptionKeyCallback.SymmetricKeyRequest request) @@ -215,9 +217,9 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * implementation delegates to specific handling methods. * * @see #handleSymmetricKeyRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, - *com.sun.xml.wss.impl.callback.EncryptionKeyCallback.SymmetricKeyRequest) + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.SymmetricKeyRequest) * @see #handleX509CertificateRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, - *com.sun.xml.wss.impl.callback.EncryptionKeyCallback.X509CertificateRequest) + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.X509CertificateRequest) */ protected final void handleEncryptionKeyCallback(EncryptionKeyCallback callback) throws IOException, UnsupportedCallbackException { @@ -239,7 +241,7 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * handleEncryptionKeyCallback(). Default implementation delegates to specific handling methods. * * @see #handleAliasSymmetricKeyRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, - *com.sun.xml.wss.impl.callback.EncryptionKeyCallback.AliasSymmetricKeyRequest) + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.AliasSymmetricKeyRequest) */ protected final void handleSymmetricKeyRequest(EncryptionKeyCallback callback, EncryptionKeyCallback.SymmetricKeyRequest request) @@ -265,11 +267,11 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * handleEncryptionKeyCallback(). Default implementation delegates to specific handling methods. * * @see #handleAliasX509CertificateRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, - *com.sun.xml.wss.impl.callback.EncryptionKeyCallback.AliasX509CertificateRequest) + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.AliasX509CertificateRequest) * @see #handleDefaultX509CertificateRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, - *com.sun.xml.wss.impl.callback.EncryptionKeyCallback.DefaultX509CertificateRequest) + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.DefaultX509CertificateRequest) * @see #handlePublicKeyBasedRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, - *com.sun.xml.wss.impl.callback.EncryptionKeyCallback.PublicKeyBasedRequest) + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.PublicKeyBasedRequest) */ protected final void handleX509CertificateRequest(EncryptionKeyCallback callback, EncryptionKeyCallback.X509CertificateRequest request) @@ -331,7 +333,7 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * implementation delegates to specific handling methods. * * @see #handlePrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureKeyCallback.PrivKeyCertRequest) + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.PrivKeyCertRequest) */ protected final void handleSignatureKeyCallback(SignatureKeyCallback callback) throws IOException, UnsupportedCallbackException { @@ -348,11 +350,11 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * handleSignatureKeyCallback(). Default implementation delegates to specific handling methods. * * @see #handleDefaultPrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureKeyCallback.DefaultPrivKeyCertRequest) + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.DefaultPrivKeyCertRequest) * @see #handleAliasPrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureKeyCallback.AliasPrivKeyCertRequest) + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.AliasPrivKeyCertRequest) * @see #handlePublicKeyBasedPrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest) + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest) */ protected final void handlePrivKeyCertRequest(SignatureKeyCallback cb, SignatureKeyCallback.PrivKeyCertRequest request) @@ -413,7 +415,7 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * Default implementation delegates to specific handling methods. * * @see #handleX509CertificateRequest(com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509CertificateRequest) + * com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509CertificateRequest) */ protected final void handleSignatureVerificationKeyCallback(SignatureVerificationKeyCallback callback) throws UnsupportedCallbackException, IOException { @@ -432,11 +434,11 @@ public class CryptographyCallbackHandler extends AbstractCallbackHandler { * handling methods. * * @see #handlePublicKeyBasedRequest(com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.PublicKeyBasedRequest) + * com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.PublicKeyBasedRequest) * @see #handleX509IssuerSerialBasedRequest(com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509IssuerSerialBasedRequest) + * com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509IssuerSerialBasedRequest) * @see #handleX509SubjectKeyIdentifierBasedRequest(com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback, - *com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509SubjectKeyIdentifierBasedRequest) + * com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509SubjectKeyIdentifierBasedRequest) */ protected final void handleX509CertificateRequest(SignatureVerificationKeyCallback callback, SignatureVerificationKeyCallback.X509CertificateRequest request) diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/MockValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/MockValidationCallbackHandler.java index a5b93dad..aff1df36 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/MockValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/MockValidationCallbackHandler.java @@ -24,6 +24,8 @@ import javax.security.auth.callback.UnsupportedCallbackException; import com.sun.xml.wss.impl.callback.CertificateValidationCallback; import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; + /** * Mock implementation of of callback handler that accepts all password and certificate validation callbacks. *

diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandler.java index 1dc70b37..ca2331f3 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandler.java @@ -25,8 +25,10 @@ import javax.security.auth.callback.UnsupportedCallbackException; import com.sun.xml.wss.impl.callback.PasswordValidationCallback; import com.sun.xml.wss.impl.callback.TimestampValidationCallback; + import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; /** * Simple callback handler that validates passwords agains a in-memory Properties object. Password diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandler.java index 82294c5f..8aa033ea 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandler.java @@ -22,8 +22,10 @@ import javax.security.auth.callback.UnsupportedCallbackException; import com.sun.xml.wss.impl.callback.PasswordCallback; import com.sun.xml.wss.impl.callback.UsernameCallback; + import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; /** * Simple callback handler that supplies a username and password to a username token at runtime. diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandler.java index d8a75845..5f35cda6 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandler.java @@ -27,17 +27,15 @@ import org.acegisecurity.AuthenticationException; import org.acegisecurity.AuthenticationManager; import org.acegisecurity.context.SecurityContextHolder; import org.acegisecurity.providers.x509.X509AuthenticationToken; + import org.springframework.util.Assert; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; /** * Callback handler that validates a certificate using an Acegi AuthenticationManager. Logic based on - * Acegi's X509ProcessingFilter. - *

- * An Acegi X509AuthenticationToken is created with the certificate as the credentials. - *

- * The configured authentication manager is expected to supply a provider which can handle this token (usually an - * instance of X509AuthenticationProvider).

+ * Acegi's X509ProcessingFilter.

An Acegi X509AuthenticationToken is created with the + * certificate as the credentials.

The configured authentication manager is expected to supply a provider which can + * handle this token (usually an instance of X509AuthenticationProvider).

*

* This class only handles CertificateValidationCallbacks, and throws an * UnsupportedCallbackException for others. diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java index 7e77d2fa..3d023503 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java @@ -29,9 +29,10 @@ import org.acegisecurity.providers.dao.cache.NullUserCache; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.userdetails.UserDetailsService; import org.acegisecurity.userdetails.UsernameNotFoundException; + import org.springframework.dao.DataAccessException; import org.springframework.util.Assert; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; import org.springframework.ws.soap.security.xwss.callback.DefaultTimestampValidator; /** diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandler.java index b35b7170..3bfd5519 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandler.java @@ -26,8 +26,9 @@ import org.acegisecurity.AuthenticationException; import org.acegisecurity.AuthenticationManager; import org.acegisecurity.context.SecurityContextHolder; import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; + import org.springframework.util.Assert; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; /** * Callback handler that validates a certificate uses an Acegi AuthenticationManager. Logic based on diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiUsernamePasswordCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiUsernamePasswordCallbackHandler.java index c903fcb0..54d7ee94 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiUsernamePasswordCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiUsernamePasswordCallbackHandler.java @@ -26,7 +26,7 @@ import org.acegisecurity.Authentication; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; /** * Callback handler that adds username/password information to a mesage using an Acegi {@link SecurityContext}. diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/AbstractJaasValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/AbstractJaasValidationCallbackHandler.java index 1cbd2747..e02ab9b6 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/AbstractJaasValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/AbstractJaasValidationCallbackHandler.java @@ -18,7 +18,7 @@ package org.springframework.ws.soap.security.xwss.callback.jaas; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; /** * Abstract base class for integrating with JAAS. Provides a login context name property. diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandler.java index 51d89f83..af90426c 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandler.java @@ -25,7 +25,8 @@ import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import com.sun.xml.wss.impl.callback.PasswordValidationCallback; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; + +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; /** * Provides basic support for integrating with JAAS and plain text passwords. diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jInterceptorTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jInterceptorTest.java new file mode 100755 index 00000000..5c4f94d2 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jInterceptorTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class AxiomWss4jInterceptorTest extends Wss4jInterceptorTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorAcegiCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorAcegiCallbackHandlerTest.java new file mode 100755 index 00000000..c5430080 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorAcegiCallbackHandlerTest.java @@ -0,0 +1,6 @@ +package org.springframework.ws.soap.security.wss4j; + +public class AxiomWss4jMessageInterceptorAcegiCallbackHandlerTest + extends Wss4jMessageInterceptorAcegiCallbackHandlerTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorEncryptionTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorEncryptionTest.java new file mode 100755 index 00000000..8863fe25 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorEncryptionTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class AxiomWss4jMessageInterceptorEncryptionTest extends Wss4jMessageInterceptorEncryptionTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorHeaderTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorHeaderTest.java new file mode 100755 index 00000000..030aa393 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorHeaderTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class AxiomWss4jMessageInterceptorHeaderTest extends Wss4jMessageInterceptorHeaderTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorSignTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorSignTest.java new file mode 100755 index 00000000..d6cc1c48 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorSignTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class AxiomWss4jMessageInterceptorSignTest extends Wss4jMessageInterceptorSignTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorTimestampTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorTimestampTest.java new file mode 100755 index 00000000..bf235956 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorTimestampTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class AxiomWss4jMessageInterceptorTimestampTest extends Wss4jMessageInterceptorTimestampTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java new file mode 100755 index 00000000..1446ae2a --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java @@ -0,0 +1,6 @@ +package org.springframework.ws.soap.security.wss4j; + +public class AxiomWss4jMessageInterceptorUsernameTokenSignatureTest + extends Wss4jMessageInterceptorUsernameTokenSignatureTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorUsernameTokenTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorUsernameTokenTest.java new file mode 100755 index 00000000..04709ff4 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/AxiomWss4jMessageInterceptorUsernameTokenTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class AxiomWss4jMessageInterceptorUsernameTokenTest extends Wss4jMessageInterceptorUsernameTokenTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jInterceptorTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jInterceptorTest.java new file mode 100755 index 00000000..d8fa08df --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jInterceptorTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class SaajWss4jInterceptorTest extends Wss4jInterceptorTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorAcegiCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorAcegiCallbackHandlerTest.java new file mode 100755 index 00000000..bbbe7f9c --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorAcegiCallbackHandlerTest.java @@ -0,0 +1,6 @@ +package org.springframework.ws.soap.security.wss4j; + +public class SaajWss4jMessageInterceptorAcegiCallbackHandlerTest + extends Wss4jMessageInterceptorAcegiCallbackHandlerTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorEncryptionTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorEncryptionTest.java new file mode 100755 index 00000000..2096df70 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorEncryptionTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class SaajWss4jMessageInterceptorEncryptionTest extends Wss4jMessageInterceptorEncryptionTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorHeaderTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorHeaderTest.java new file mode 100755 index 00000000..10fc8fe0 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorHeaderTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class SaajWss4jMessageInterceptorHeaderTest extends Wss4jMessageInterceptorHeaderTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorSignTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorSignTest.java new file mode 100755 index 00000000..25ee62ff --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorSignTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class SaajWss4jMessageInterceptorSignTest extends Wss4jMessageInterceptorSignTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorTimestampTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorTimestampTest.java new file mode 100755 index 00000000..a0e892fc --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorTimestampTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class SaajWss4jMessageInterceptorTimestampTest extends Wss4jMessageInterceptorTimestampTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorUsernameTokenSignatureTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorUsernameTokenSignatureTest.java new file mode 100755 index 00000000..4ce023d3 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorUsernameTokenSignatureTest.java @@ -0,0 +1,6 @@ +package org.springframework.ws.soap.security.wss4j; + +public class SaajWss4jMessageInterceptorUsernameTokenSignatureTest + extends Wss4jMessageInterceptorUsernameTokenSignatureTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorUsernameTokenTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorUsernameTokenTest.java new file mode 100755 index 00000000..2372e257 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/SaajWss4jMessageInterceptorUsernameTokenTest.java @@ -0,0 +1,5 @@ +package org.springframework.ws.soap.security.wss4j; + +public class SaajWss4jMessageInterceptorUsernameTokenTest extends Wss4jMessageInterceptorUsernameTokenTestCase { + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jInterceptorTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jInterceptorTestCase.java new file mode 100755 index 00000000..59e05247 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jInterceptorTestCase.java @@ -0,0 +1,57 @@ +package org.springframework.ws.soap.security.wss4j; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.WsSecuritySecurementException; +import org.springframework.ws.soap.security.WsSecurityValidationException; + +public abstract class Wss4jInterceptorTestCase extends Wss4jTestCase { + + public void testhandleRequest() throws Exception { + SoapMessage request = loadMessage("empty-soap.xml"); + final Object requestMessage = getMessage(request); + SoapMessage validatedRequest = loadMessage("empty-soap.xml"); + final Object validatedRequestMessage = getMessage(validatedRequest); + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor() { + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecuritySecurementException { + fail("secure not expected"); + } + + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { + assertEquals("Invalid message", requestMessage, getMessage(soapMessage)); + setMessage(soapMessage, validatedRequestMessage); + } + }; + MessageContext context = new DefaultMessageContext(request, getMessageFactory()); + interceptor.handleRequest(context, null); + assertEquals("Invalid request", validatedRequestMessage, getMessage((SoapMessage) context.getRequest())); + } + + public void testhandleResponse() throws Exception { + SoapMessage securedResponse = loadMessage("empty-soap.xml"); + final Object securedResponseMessage = getMessage(securedResponse); + + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor() { + + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecuritySecurementException { + setMessage(soapMessage, securedResponseMessage); + } + + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { + fail("validate not expected"); + } + + }; + SoapMessage request = loadMessage("empty-soap.xml"); + MessageContext context = new DefaultMessageContext(request, getMessageFactory()); + context.getResponse(); + interceptor.handleResponse(context, null); + assertEquals("Invalid response", securedResponseMessage, getMessage((SoapMessage) context.getResponse())); + } + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorAcegiCallbackHandlerTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorAcegiCallbackHandlerTestCase.java new file mode 100755 index 00000000..bb040934 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorAcegiCallbackHandlerTestCase.java @@ -0,0 +1,79 @@ +package org.springframework.ws.soap.security.wss4j; + +import java.util.Properties; + +import org.acegisecurity.Authentication; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.userdetails.memory.InMemoryDaoImpl; +import org.apache.ws.security.WSConstants; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.NameMatchMethodPointcutAdvisor; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.wss4j.callback.acegi.AcegiCallbackHandler; +import org.springframework.ws.soap.security.wss4j.callback.acegi.AcegiSecurityContextUpdateAdvice; + +public abstract class Wss4jMessageInterceptorAcegiCallbackHandlerTestCase extends Wss4jTestCase { + + private Properties users = new Properties(); + + protected void onSetup() throws Exception { + users.setProperty("Bert", "Ernie,ROLE_TEST"); + } + + public void testValidateUsernameTokenDigest() throws Exception { + EndpointInterceptor interceptor = prepareInterceptor("UsernameToken", true, true); + SoapMessage message = loadMessage("usernameTokenDigest-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getMessageFactory()); + interceptor.handleRequest(messageContext, null); + assertValidateUsernameToken(message); + } + + protected void assertValidateUsernameToken(SoapMessage message) throws Exception { + Object result = getMessage(message); + assertNotNull("No result returned", result); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", + getDocument(message)); + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + assertNotNull("authentication must not be null", authentication); + } + + protected EndpointInterceptor prepareInterceptor(String actions, boolean validating, boolean digest) + throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + if (validating) { + interceptor.setValidationActions(actions); + } + else { + interceptor.setSecurementActions(actions); + } + AcegiCallbackHandler callbackHandler = new AcegiCallbackHandler(); + InMemoryDaoImpl userDetailsService = new InMemoryDaoImpl(); + userDetailsService.setUserProperties(users); + userDetailsService.afterPropertiesSet(); + callbackHandler.setUserDetailsService(userDetailsService); + if (digest) { + callbackHandler.setPasswordDigestRequired(true); + callbackHandler.setPasswordPlainTextRequired(false); + interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST); + } + else { + callbackHandler.setPasswordDigestRequired(false); + callbackHandler.setPasswordPlainTextRequired(true); + interceptor.setSecurementPasswordType(WSConstants.PW_TEXT); + } + interceptor.setValidationCallbackHandler(callbackHandler); + interceptor.afterPropertiesSet(); + + ProxyFactory factory = new ProxyFactory(interceptor); + AcegiSecurityContextUpdateAdvice advice = new AcegiSecurityContextUpdateAdvice(); + NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice); + advisor.setMappedName("handleRequest"); + factory.addAdvisor(advisor); + return (EndpointInterceptor) factory.getProxy(); + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorEncryptionTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorEncryptionTestCase.java new file mode 100755 index 00000000..5e107ff3 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorEncryptionTestCase.java @@ -0,0 +1,68 @@ +package org.springframework.ws.soap.security.wss4j; + +import java.util.Properties; + +import org.apache.ws.security.components.crypto.Crypto; +import org.w3c.dom.Document; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.wss4j.callback.SimpleCallbackHandler; +import org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean; + +public abstract class Wss4jMessageInterceptorEncryptionTestCase extends Wss4jTestCase { + + protected Wss4jSecurityInterceptor interceptor; + + protected void onSetup() throws Exception { + interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidationActions("Encrypt"); + interceptor.setSecurementActions("Encrypt"); + + SimpleCallbackHandler callbackHandler = new SimpleCallbackHandler(); + callbackHandler.setKeyPassword("123456"); + interceptor.setValidationCallbackHandler(callbackHandler); + + CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean(); + + Properties cryptoFactoryBeanConfig = new Properties(); + cryptoFactoryBeanConfig.setProperty("org.apache.ws.security.crypto.provider", + "org.apache.ws.security.components.crypto.Merlin"); + cryptoFactoryBeanConfig.setProperty("org.apache.ws.security.crypto.merlin.keystore.type", "jceks"); + cryptoFactoryBeanConfig.setProperty("org.apache.ws.security.crypto.merlin.keystore.password", "123456"); + + // from the class path + cryptoFactoryBeanConfig.setProperty("org.apache.ws.security.crypto.merlin.file", "private.jks"); + cryptoFactoryBean.setConfiguration(cryptoFactoryBeanConfig); + cryptoFactoryBean.afterPropertiesSet(); + interceptor.setValidationDecryptionCrypto((Crypto) cryptoFactoryBean + .getObject()); + interceptor.setSecurementEncryptionCrypto((Crypto) cryptoFactoryBean + .getObject()); + + interceptor.afterPropertiesSet(); + } + + public void testDecryptRequest() throws Exception { + SoapMessage message = loadMessage("encrypted-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getMessageFactory()); + interceptor.validateMessage(message, messageContext); + Document document = getDocument((SoapMessage) messageContext.getRequest()); + assertXpathEvaluatesTo("Decryption error", "Hello", "/SOAP-ENV:Envelope/SOAP-ENV:Body/echo:echoRequest/text()", + document); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", + getDocument(message)); + } + + public void testEncryptResponse() throws Exception { + SoapMessage message = loadMessage("empty-soap.xml"); + MessageContext messageContext = getMessageContext(message); + interceptor.setSecurementEncryptionUser("rsakey"); + interceptor.secureMessage(message, messageContext); + Document document = getDocument(message); + assertXpathExists("Encryption error", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/xenc:EncryptedKey", + document); + //TODO see why the clear message appears in the unit test + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorHeaderTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorHeaderTestCase.java new file mode 100755 index 00000000..5c845f99 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorHeaderTestCase.java @@ -0,0 +1,55 @@ +package org.springframework.ws.soap.security.wss4j; + +import java.util.Iterator; +import java.util.Properties; +import javax.xml.namespace.QName; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.wss4j.callback.SimpleCallbackHandler; + +public abstract class Wss4jMessageInterceptorHeaderTestCase extends Wss4jTestCase { + + private Wss4jSecurityInterceptor interceptor; + + protected void onSetup() throws Exception { + Properties users = new Properties(); + users.setProperty("Bert", "Ernie"); + interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidateRequest(true); + interceptor.setSecureResponse(true); + interceptor.setValidationActions("UsernameToken"); + SimpleCallbackHandler callbackHandler = new SimpleCallbackHandler(); + callbackHandler.setUsers(users); + interceptor.setValidationCallbackHandler(callbackHandler); + interceptor.afterPropertiesSet(); + } + + public void testValidateUsernameTokenPlainText() throws Exception { + SoapMessage message = loadMessage("usernameTokenPlainTextWithHeaders-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getMessageFactory()); + interceptor.validateMessage(message, messageContext); + Object result = getMessage(message); + assertNotNull("No result returned", result); + + for (Iterator i = message.getEnvelope().getHeader() + .examineAllHeaderElements(); i.hasNext();) { + SoapHeaderElement element = (SoapHeaderElement) i.next(); + QName name = element.getName(); + if (name + .getNamespaceURI() + .equals("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")) { + fail("Security Header not removed"); + } + + } + + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", + getDocument(message)); + assertXpathExists("header1 not found", "/SOAP-ENV:Envelope/SOAP-ENV:Header/header1", getDocument(message)); + assertXpathExists("header2 not found", "/SOAP-ENV:Envelope/SOAP-ENV:Header/header2", getDocument(message)); + + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorSignTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorSignTestCase.java new file mode 100755 index 00000000..038707fc --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorSignTestCase.java @@ -0,0 +1,88 @@ +package org.springframework.ws.soap.security.wss4j; + +import java.util.Properties; + +import org.apache.ws.security.components.crypto.Crypto; +import org.w3c.dom.Document; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.wss4j.callback.SimpleCallbackHandler; +import org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean; + +public abstract class Wss4jMessageInterceptorSignTestCase extends Wss4jTestCase { + + protected Wss4jSecurityInterceptor interceptor; + + protected void onSetup() throws Exception { + interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidationActions("Signature"); + SimpleCallbackHandler callbackHandler = new SimpleCallbackHandler(); + interceptor.setValidationCallbackHandler(callbackHandler); + + CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean(); + Properties cryptoFactoryBeanConfig = new Properties(); + cryptoFactoryBeanConfig.setProperty("org.apache.ws.security.crypto.provider", + "org.apache.ws.security.components.crypto.Merlin"); + cryptoFactoryBeanConfig.setProperty("org.apache.ws.security.crypto.merlin.keystore.type", "jceks"); + cryptoFactoryBeanConfig.setProperty("org.apache.ws.security.crypto.merlin.keystore.password", "123456"); + + // from the class path + cryptoFactoryBeanConfig.setProperty("org.apache.ws.security.crypto.merlin.file", "private.jks"); + cryptoFactoryBean.setConfiguration(cryptoFactoryBeanConfig); + cryptoFactoryBean.afterPropertiesSet(); + interceptor.setValidationSignatureCrypto((Crypto) cryptoFactoryBean + .getObject()); + interceptor.setSecurementSignatureCrypto((Crypto) cryptoFactoryBean + .getObject()); + interceptor.afterPropertiesSet(); + + } + + public void testValidateCertificate() throws Exception { + SoapMessage message = loadMessage("signed-soap.xml"); + + MessageContext messageContext = new DefaultMessageContext(message, getMessageFactory()); + interceptor.validateMessage(message, messageContext); + Object result = getMessage(message); + assertNotNull("No result returned", result); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", + getDocument(message)); + } + + public void testValidateCertificateWithSignatureConfirmation() throws Exception { + SoapMessage message = loadMessage("signed-soap.xml"); + MessageContext messageContext = getMessageContext(message); + interceptor.setEnableSignatureConfirmation(true); + interceptor.validateMessage(message, messageContext); + WebServiceMessage response = messageContext.getResponse(); + interceptor.secureMessage(message, messageContext); + assertNotNull("No result returned", response); + Document document = getDocument((SoapMessage) response); + message.writeTo(System.out); + assertXpathExists("Absent SignatureConfirmation element", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse11:SignatureConfirmation", document); + } + + public void testSignResponse() throws Exception { + interceptor.setSecurementActions("Signature"); + interceptor.setEnableSignatureConfirmation(false); + interceptor.setSecurementPassword("123456"); + interceptor.setSecurementUsername("rsaKey"); + SoapMessage message = loadMessage("empty-soap.xml"); + MessageContext messageContext = getMessageContext(message); + + // interceptor.setSecurementSignatureKeyIdentifier("IssuerSerial"); + + interceptor.secureMessage(message, messageContext); + + Document document = getDocument(message); + assertXpathExists("Absent SignatureConfirmation element", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/ds:Signature", document); + + + } + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorTimestampTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorTimestampTestCase.java new file mode 100755 index 00000000..a2b2d3a0 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorTimestampTestCase.java @@ -0,0 +1,78 @@ +package org.springframework.ws.soap.security.wss4j; + +import java.lang.reflect.Field; + +import org.w3c.dom.Document; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; + +public abstract class Wss4jMessageInterceptorTimestampTestCase extends Wss4jTestCase { + + public void testAddTimestamp() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("Timestamp"); + interceptor.afterPropertiesSet(); + SoapMessage message = loadMessage("empty-soap.xml"); + MessageContext context = getMessageContext(message); + interceptor.secureMessage(message, context); + Document document = getDocument(message); + assertXpathExists("timestamp header not found", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsu:Timestamp", document); + } + + public void testValidateTimestamp() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidationActions("Timestamp"); + interceptor.afterPropertiesSet(); + SoapMessage message = getMessageWithTimestamp(); + + MessageContext context = new DefaultMessageContext(message, getMessageFactory()); + interceptor.validateMessage(message, context); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", + getDocument(message)); + } + + public void testValidateTimestampWithTtl() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor() { + public void setTimeToLive(int t) { + try { + Field ttl = Wss4jSecurityInterceptor.class + .getDeclaredField("timeToLive"); + ttl.setAccessible(true); + ttl.set(this, new Integer(t)); + + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + interceptor.setValidationActions("Timestamp"); + interceptor.setTimeToLive(-10); + interceptor.setTimestampStrict(true); + interceptor.afterPropertiesSet(); + SoapMessage message = getMessageWithTimestamp(); + MessageContext context = new DefaultMessageContext(message, getMessageFactory()); + + try { + interceptor.validateMessage(message, context); + } + catch (Wss4jSecurityValidationException ex) { + // expected + return; + } + fail("Time to live validation failed"); + } + + private SoapMessage getMessageWithTimestamp() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("Timestamp"); + interceptor.afterPropertiesSet(); + SoapMessage message = loadMessage("empty-soap.xml"); + MessageContext context = getMessageContext(message); + interceptor.secureMessage(message, context); + return message; + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenSignatureTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenSignatureTestCase.java new file mode 100755 index 00000000..c2535475 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenSignatureTestCase.java @@ -0,0 +1,27 @@ +package org.springframework.ws.soap.security.wss4j; + +import org.w3c.dom.Document; + +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; + +public abstract class Wss4jMessageInterceptorUsernameTokenSignatureTestCase extends Wss4jTestCase { + + public void testAddUsernameTokenSignature() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("UsernameTokenSignature"); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + interceptor.afterPropertiesSet(); + SoapMessage message = loadMessage("empty-soap.xml"); + MessageContext context = getMessageContext(message); + interceptor.secureMessage(message, context); + + Document doc = getDocument(message); + assertXpathEvaluatesTo("Invalid Username", "Bert", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Username/text()", doc); + assertXpathExists("Invalid Password", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Password[@Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest']/text()", + doc); + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenTestCase.java new file mode 100755 index 00000000..5c82bb16 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenTestCase.java @@ -0,0 +1,115 @@ +package org.springframework.ws.soap.security.wss4j; + +import java.util.Properties; + +import org.apache.ws.security.WSConstants; +import org.w3c.dom.Document; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.wss4j.callback.SimpleCallbackHandler; + +public abstract class Wss4jMessageInterceptorUsernameTokenTestCase extends Wss4jTestCase { + + private Properties users = new Properties(); + + protected void onSetup() throws Exception { + users.setProperty("Bert", "Ernie"); + } + + public void testValidateUsernameTokenPlainText() throws Exception { + Wss4jSecurityInterceptor interceptor = prepareInterceptor("UsernameToken", true, false); + SoapMessage message = loadMessage("usernameTokenPlainText-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getMessageFactory()); + interceptor.validateMessage(message, messageContext); + assertValidateUsernameToken(message); + } + + public void testValidateUsernameTokenDigest() throws Exception { + Wss4jSecurityInterceptor interceptor = prepareInterceptor("UsernameToken", true, true); + SoapMessage message = loadMessage("usernameTokenDigest-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getMessageFactory()); + interceptor.validateMessage(message, messageContext); + assertValidateUsernameToken(message); + } + + public void testAddUsernameTokenPlainText() throws Exception { + Wss4jSecurityInterceptor interceptor = prepareInterceptor("UsernameToken", false, false); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + SoapMessage message = loadMessage("empty-soap.xml"); + + MessageContext messageContext = getMessageContext(message); + + interceptor.secureMessage(message, messageContext); + assertAddUsernameTokenPlainText(message); + } + + public void testAddUsernameTokenDigest() throws Exception { + Wss4jSecurityInterceptor interceptor = prepareInterceptor("UsernameToken", false, true); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + SoapMessage message = loadMessage("empty-soap.xml"); + + MessageContext messageContext = getMessageContext(message); + interceptor.secureMessage(message, messageContext); + assertAddUsernameTokenDigest(message); + } + + protected void assertValidateUsernameToken(SoapMessage message) throws Exception { + Object result = getMessage(message); + assertNotNull("No result returned", result); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", + getDocument(message)); + } + + protected void assertAddUsernameTokenPlainText(SoapMessage message) throws Exception { + Object result = getMessage(message); + assertNotNull("No result returned", result); + Document doc = getDocument(message); + assertXpathEvaluatesTo("Invalid Username", "Bert", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Username/text()", doc); + assertXpathEvaluatesTo("Invalid Password", "Ernie", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Password[@Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText']/text()", + doc); + } + + protected void assertAddUsernameTokenDigest(SoapMessage message) throws Exception { + Object result = getMessage(message); + Document doc = getDocument(message); + assertNotNull("No result returned", result); + assertXpathEvaluatesTo("Invalid Username", "Bert", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Username/text()", doc); + assertXpathExists("Password does not exist", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Password[@Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest']", + doc); + + } + + protected Wss4jSecurityInterceptor prepareInterceptor(String actions, boolean validating, boolean digest) + throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + if (validating) { + interceptor.setValidationActions(actions); + } + else { + interceptor.setSecurementActions(actions); + } + SimpleCallbackHandler callbackHandler = new SimpleCallbackHandler(); + callbackHandler.setUsers(users); + if (digest) { + callbackHandler.setPasswordDigestRequired(true); + callbackHandler.setPasswordPlainTextRequired(false); + interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST); + } + else { + callbackHandler.setPasswordDigestRequired(false); + callbackHandler.setPasswordPlainTextRequired(true); + interceptor.setSecurementPasswordType(WSConstants.PW_TEXT); + } + interceptor.setValidationCallbackHandler(callbackHandler); + interceptor.afterPropertiesSet(); + return interceptor; + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jTestCase.java new file mode 100755 index 00000000..9a620525 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jTestCase.java @@ -0,0 +1,200 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPMessage; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; + +import junit.framework.TestCase; +import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.xml.xpath.XPathExpression; +import org.springframework.xml.xpath.XPathExpressionFactory; + +public abstract class Wss4jTestCase extends TestCase { + + protected MessageFactory messageFactory; + + protected final boolean axiomTest = this.getClass().getSimpleName() + .startsWith("Axiom"); + + protected final boolean saajTest = this.getClass().getSimpleName() + .startsWith("Saaj"); + + protected Map namespaces; + + protected final void setUp() throws Exception { + if (!axiomTest && !saajTest) { + throw new IllegalArgumentException("test class name must statrt with either Axiom or Saaj"); + } + messageFactory = MessageFactory.newInstance(); + namespaces = new HashMap(); + namespaces.put("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"); + namespaces.put("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); + namespaces.put("ds", "http://www.w3.org/2000/09/xmldsig#"); + namespaces.put("xenc", "http://www.w3.org/2001/04/xmlenc#"); +// namespaces.put("wsse11", "http://docs.oasis-open.org/wss/2005/xx/oasis-2005xx-wss-wssecurity-secext-1.1.xsd"); + namespaces.put("wsse11", "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"); + namespaces.put("echo", "http://www.springframework.org/spring-ws/samples/echo"); + namespaces.put("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); + onSetup(); + } + + protected void assertXpathEvaluatesTo(String message, + String expectedValue, + String xpathExpression, + Document document) { + XPathExpression expression = XPathExpressionFactory + .createXPathExpression(xpathExpression, namespaces); + String actualValue = expression.evaluateAsString(document); + assertEquals(message, expectedValue, actualValue); + } + + protected void assertXpathExists(String message, String xpathExpression, Document document) { + XPathExpression expression = XPathExpressionFactory + .createXPathExpression(xpathExpression, namespaces); + Node node = expression.evaluateAsNode(document); + assertNotNull(message, node); + } + + protected void assertXpathNotExists(String message, String xpathExpression, Document document) { + XPathExpression expression = XPathExpressionFactory + .createXPathExpression(xpathExpression, namespaces); + Node node = expression.evaluateAsNode(document); + assertNull(message, node); + } + + protected SaajSoapMessage loadSaajMessage(String fileName) throws Exception { + MimeHeaders mimeHeaders = new MimeHeaders(); + mimeHeaders.addHeader("Content-Type", "text/xml"); + Resource resource = new ClassPathResource(fileName, getClass()); + InputStream is = resource.getInputStream(); + try { + assertTrue("Could not load SAAJ message [" + resource + "]", resource.exists()); + is = resource.getInputStream(); + return new SaajSoapMessage(messageFactory.createMessage(mimeHeaders, is)); + } + finally { + is.close(); + } + } + + protected AxiomSoapMessage loadAxiomMessage(String fileName) throws Exception { + Resource resource = new ClassPathResource(fileName, getClass()); + InputStream is = resource.getInputStream(); + try { + assertTrue("Could not load Axiom message [" + resource + "]", resource.exists()); + is = resource.getInputStream(); + + XMLStreamReader parser = XMLInputFactory.newInstance() + .createXMLStreamReader(is); + StAXSOAPModelBuilder builder = new StAXSOAPModelBuilder(parser, null); + org.apache.axiom.soap.SOAPMessage soapMessage = builder + .getSoapMessage(); + return new AxiomSoapMessage(soapMessage, "", true); + } + finally { + is.close(); + } + } + + protected Object getMessage(SoapMessage soapMessage) { + if (soapMessage instanceof SaajSoapMessage) { + return ((SaajSoapMessage) soapMessage).getSaajMessage(); + } + if (soapMessage instanceof AxiomSoapMessage) { + return ((AxiomSoapMessage) soapMessage).getAxiomMessage(); + + } + throw new IllegalArgumentException("Illegal message: " + soapMessage); + } + + protected void setMessage(SoapMessage soapMessage, Object message) { + if (soapMessage instanceof SaajSoapMessage) { + ((SaajSoapMessage) soapMessage) + .setSaajMessage((SOAPMessage) message); + return; + } + if (soapMessage instanceof AxiomSoapMessage) { + ((AxiomSoapMessage) soapMessage) + .setAxiomMessage((org.apache.axiom.soap.SOAPMessage) message); + return; + } + throw new IllegalArgumentException("Illegal message: " + message); + } + + protected void onSetup() throws Exception { + } + + protected SoapMessage loadMessage(String fileName) throws Exception { + if (axiomTest) { + return loadAxiomMessage(fileName); + } + if (saajTest) { + return loadSaajMessage(fileName); + } + throw new IllegalArgumentException(); + } + + protected WebServiceMessageFactory getMessageFactory() throws Exception { + if (axiomTest) { + return new AxiomSoapMessageFactory(); + } + if (saajTest) { + return new SaajSoapMessageFactory(messageFactory); + } + throw new IllegalArgumentException(); + } + + protected Document getDocument(SoapMessage message) throws Exception { + if (axiomTest) { + return AxiomUtils.toDocument(((AxiomSoapMessage) message).getAxiomMessage().getSOAPEnvelope()); + } + if (saajTest) { + return ((SaajSoapMessage) message).getSaajMessage().getSOAPPart(); + } + throw new IllegalArgumentException(); + } + + protected MessageContext getMessageContext(final SoapMessage response) throws Exception { + return new DefaultMessageContext(response, getMessageFactory()) { + public WebServiceMessage getResponse() { + return response; + } + }; + } + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/support/CryptoFactoryBeanTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/support/CryptoFactoryBeanTest.java new file mode 100644 index 00000000..bc66a6da --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/support/CryptoFactoryBeanTest.java @@ -0,0 +1,47 @@ +/* + * Copyright ${YEAR} the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.wss4j.support; + +import java.util.Properties; + +import junit.framework.TestCase; +import org.apache.ws.security.components.crypto.Crypto; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.util.ClassUtils; + +public class CryptoFactoryBeanTest extends TestCase { + + private CryptoFactoryBean factoryBean; + + protected void setUp() throws Exception { + factoryBean = new CryptoFactoryBean(); + } + + public void testMerlin() throws Exception { + Properties configuration = + PropertiesLoaderUtils.loadProperties(new ClassPathResource("merlin.properties", getClass())); + factoryBean.setConfiguration(configuration); + factoryBean.setBeanClassLoader(ClassUtils.getDefaultClassLoader()); + factoryBean.afterPropertiesSet(); + + Object result = factoryBean.getObject(); + assertNotNull("No result", result); + assertTrue("Not a crypto instance", result instanceof Crypto); + } +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorKeyStoreTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/AbstractXwssMessageInterceptorKeyStoreTestCase.java similarity index 92% rename from security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorKeyStoreTestCase.java rename to security/src/test/java/org/springframework/ws/soap/security/xwss/AbstractXwssMessageInterceptorKeyStoreTestCase.java index bf1660c6..3d757002 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorKeyStoreTestCase.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/AbstractXwssMessageInterceptorKeyStoreTestCase.java @@ -21,7 +21,7 @@ import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.X509Certificate; -public abstract class XwssMessageInterceptorKeyStoreTestCase extends XwssMessageInterceptorTestCase { +public abstract class AbstractXwssMessageInterceptorKeyStoreTestCase extends AbstractXwssMessageInterceptorTestCase { protected X509Certificate certificate; diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/AbstractXwssMessageInterceptorTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/AbstractXwssMessageInterceptorTestCase.java new file mode 100644 index 00000000..ca408f4a --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/AbstractXwssMessageInterceptorTestCase.java @@ -0,0 +1,98 @@ +/* + * Copyright 2006 the original author or authors. + * + * Licensed 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.springframework.ws.soap.security.xwss; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.xml.xpath.XPathExpression; +import org.springframework.xml.xpath.XPathExpressionFactory; + +public abstract class AbstractXwssMessageInterceptorTestCase extends XMLTestCase { + + protected XwsSecurityInterceptor interceptor; + + private MessageFactory messageFactory; + + private Map namespaces; + + protected final void setUp() throws Exception { + interceptor = new XwsSecurityInterceptor(); + messageFactory = MessageFactory.newInstance(); + namespaces = new HashMap(); + namespaces.put("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"); + namespaces.put("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); + namespaces.put("ds", "http://www.w3.org/2000/09/xmldsig#"); + namespaces.put("xenc", "http://www.w3.org/2001/04/xmlenc#"); + onSetup(); + } + + protected void assertXpathEvaluatesTo(String message, + String expectedValue, + String xpathExpression, + SOAPMessage soapMessage) { + XPathExpression expression = XPathExpressionFactory.createXPathExpression(xpathExpression, namespaces); + Document document = soapMessage.getSOAPPart(); + String actualValue = expression.evaluateAsString(document); + assertEquals(message, expectedValue, actualValue); + } + + protected void assertXpathExists(String message, String xpathExpression, SOAPMessage soapMessage) { + XPathExpression expression = XPathExpressionFactory.createXPathExpression(xpathExpression, namespaces); + Document document = soapMessage.getSOAPPart(); + Node node = expression.evaluateAsNode(document); + assertNotNull(message, node); + } + + protected void assertXpathNotExists(String message, String xpathExpression, SOAPMessage soapMessage) { + XPathExpression expression = XPathExpressionFactory.createXPathExpression(xpathExpression, namespaces); + Document document = soapMessage.getSOAPPart(); + Node node = expression.evaluateAsNode(document); + assertNull(message, node); + } + + protected SaajSoapMessage loadSaajMessage(String fileName) throws SOAPException, IOException { + MimeHeaders mimeHeaders = new MimeHeaders(); + mimeHeaders.addHeader("Content-Type", "text/xml"); + Resource resource = new ClassPathResource(fileName, getClass()); + InputStream is = resource.getInputStream(); + try { + assertTrue("Could not load SAAJ message [" + resource + "]", resource.exists()); + is = resource.getInputStream(); + return new SaajSoapMessage(messageFactory.createMessage(mimeHeaders, is)); + } + finally { + is.close(); + } + } + + protected void onSetup() throws Exception { + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java index c9245b2f..ee204b7c 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java @@ -41,11 +41,13 @@ public class XwsSecurityInterceptorTest extends TestCase { final SOAPMessage validatedRequest = messageFactory.createMessage(); XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { - protected void secureMessage(SoapMessage soapMessage) throws XwsSecuritySecurementException { + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws XwsSecuritySecurementException { fail("secure not expected"); } - protected void validateMessage(SoapMessage message) throws WsSecurityValidationException { + protected void validateMessage(SoapMessage message, MessageContext messageContext) + throws WsSecurityValidationException { SaajSoapMessage saajSoapMessage = (SaajSoapMessage) message; assertEquals("Invalid message", request, saajSoapMessage.getSaajMessage()); saajSoapMessage.setSaajMessage(validatedRequest); @@ -62,12 +64,14 @@ public class XwsSecurityInterceptorTest extends TestCase { final SOAPMessage securedResponse = messageFactory.createMessage(); XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { - protected void secureMessage(SoapMessage message) throws XwsSecuritySecurementException { + protected void secureMessage(SoapMessage message, MessageContext messageContext) + throws XwsSecuritySecurementException { SaajSoapMessage saajSoapMessage = (SaajSoapMessage) message; saajSoapMessage.setSaajMessage(securedResponse); } - protected void validateMessage(SoapMessage soapMessage) throws WsSecurityValidationException { + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { fail("validate not expected"); } @@ -85,13 +89,15 @@ public class XwsSecurityInterceptorTest extends TestCase { final SOAPMessage securedRequest = messageFactory.createMessage(); XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { - protected void secureMessage(SoapMessage soapMessage) throws XwsSecuritySecurementException { + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws XwsSecuritySecurementException { SaajSoapMessage saajSoapMessage = (SaajSoapMessage) soapMessage; assertEquals("Invalid message", request, saajSoapMessage.getSaajMessage()); saajSoapMessage.setSaajMessage(securedRequest); } - protected void validateMessage(SoapMessage message) throws WsSecurityValidationException { + protected void validateMessage(SoapMessage message, MessageContext messageContext) + throws WsSecurityValidationException { fail("validate not expected"); } @@ -106,11 +112,13 @@ public class XwsSecurityInterceptorTest extends TestCase { final SOAPMessage validatedResponse = messageFactory.createMessage(); XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { - protected void secureMessage(SoapMessage message) throws XwsSecuritySecurementException { + protected void secureMessage(SoapMessage message, MessageContext messageContext) + throws XwsSecuritySecurementException { fail("secure not expected"); } - protected void validateMessage(SoapMessage soapMessage) throws WsSecurityValidationException { + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { SaajSoapMessage saajSoapMessage = (SaajSoapMessage) soapMessage; saajSoapMessage.setSaajMessage(validatedResponse); } diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorEncryptTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorEncryptTest.java index 1789977f..0f878b70 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorEncryptTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorEncryptTest.java @@ -24,9 +24,9 @@ import com.sun.xml.wss.impl.callback.EncryptionKeyCallback; import org.springframework.core.io.ClassPathResource; import org.springframework.ws.soap.saaj.SaajSoapMessage; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; -public class XwssMessageInterceptorEncryptTest extends XwssMessageInterceptorKeyStoreTestCase { +public class XwssMessageInterceptorEncryptTest extends AbstractXwssMessageInterceptorKeyStoreTestCase { public void testEncryptDefaultCertificate() throws Exception { interceptor.setPolicyConfiguration(new ClassPathResource("encrypt-config.xml", getClass())); @@ -53,7 +53,7 @@ public class XwssMessageInterceptorEncryptTest extends XwssMessageInterceptorKey interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("empty-soap.xml"); - interceptor.secureMessage(message); + interceptor.secureMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathExists("BinarySecurityToken does not exist", @@ -87,7 +87,7 @@ public class XwssMessageInterceptorEncryptTest extends XwssMessageInterceptorKey interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("empty-soap.xml"); - interceptor.secureMessage(message); + interceptor.secureMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathExists("BinarySecurityToken does not exist", diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorSignTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorSignTest.java index 90a00a95..bd5fc9a6 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorSignTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorSignTest.java @@ -23,11 +23,12 @@ import javax.xml.soap.SOAPMessage; import com.sun.xml.wss.impl.callback.CertificateValidationCallback; import com.sun.xml.wss.impl.callback.SignatureKeyCallback; + import org.springframework.core.io.ClassPathResource; import org.springframework.ws.soap.saaj.SaajSoapMessage; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; -public class XwssMessageInterceptorSignTest extends XwssMessageInterceptorKeyStoreTestCase { +public class XwssMessageInterceptorSignTest extends AbstractXwssMessageInterceptorKeyStoreTestCase { public void testSignDefaultCertificate() throws Exception { interceptor.setPolicyConfiguration(new ClassPathResource("sign-config.xml", getClass())); @@ -54,7 +55,7 @@ public class XwssMessageInterceptorSignTest extends XwssMessageInterceptorKeySto interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("empty-soap.xml"); - interceptor.secureMessage(message); + interceptor.secureMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathExists("BinarySecurityToken does not exist", @@ -89,7 +90,7 @@ public class XwssMessageInterceptorSignTest extends XwssMessageInterceptorKeySto interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("empty-soap.xml"); - interceptor.secureMessage(message); + interceptor.secureMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathExists("BinarySecurityToken does not exist", @@ -120,7 +121,7 @@ public class XwssMessageInterceptorSignTest extends XwssMessageInterceptorKeySto interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("signed-soap.xml"); - interceptor.validateMessage(message); + interceptor.validateMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", result); diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorUsernameTokenTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorUsernameTokenTest.java index 66efda87..c8f35ea1 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorUsernameTokenTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorUsernameTokenTest.java @@ -24,11 +24,12 @@ import com.sun.xml.wss.impl.callback.PasswordCallback; import com.sun.xml.wss.impl.callback.PasswordValidationCallback; import com.sun.xml.wss.impl.callback.TimestampValidationCallback; import com.sun.xml.wss.impl.callback.UsernameCallback; + import org.springframework.core.io.ClassPathResource; import org.springframework.ws.soap.saaj.SaajSoapMessage; -import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; -public class XwssMessageInterceptorUsernameTokenTest extends XwssMessageInterceptorTestCase { +public class XwssMessageInterceptorUsernameTokenTest extends AbstractXwssMessageInterceptorTestCase { public void testAddUsernameTokenDigest() throws Exception { interceptor.setPolicyConfiguration(new ClassPathResource("usernameToken-digest-config.xml", getClass())); @@ -50,7 +51,7 @@ public class XwssMessageInterceptorUsernameTokenTest extends XwssMessageIntercep interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("empty-soap.xml"); - interceptor.secureMessage(message); + interceptor.secureMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathEvaluatesTo("Invalid Username", "Bert", @@ -80,7 +81,7 @@ public class XwssMessageInterceptorUsernameTokenTest extends XwssMessageIntercep interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("empty-soap.xml"); - interceptor.secureMessage(message); + interceptor.secureMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathEvaluatesTo("Invalid Username", "Bert", @@ -122,7 +123,7 @@ public class XwssMessageInterceptorUsernameTokenTest extends XwssMessageIntercep interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("usernameTokenPlainText-soap.xml"); - interceptor.validateMessage(message); + interceptor.validateMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", result); @@ -161,7 +162,7 @@ public class XwssMessageInterceptorUsernameTokenTest extends XwssMessageIntercep interceptor.setCallbackHandler(handler); interceptor.afterPropertiesSet(); SaajSoapMessage message = loadSaajMessage("usernameTokenDigest-soap.xml"); - interceptor.validateMessage(message); + interceptor.validateMessage(message, null); SOAPMessage result = message.getSaajMessage(); assertNotNull("No result returned", result); assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", result); diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChainTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChainTest.java index 13d40537..02a9c5a3 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChainTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChainTest.java @@ -22,6 +22,8 @@ import javax.security.auth.callback.UnsupportedCallbackException; import junit.framework.TestCase; +import org.springframework.ws.soap.security.callback.CallbackHandlerChain; + public class CallbackHandlerChainTest extends TestCase { private CallbackHandler supported = new CallbackHandler() { diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/echo-response.xml b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/echo-response.xml new file mode 100755 index 00000000..dcef295a --- /dev/null +++ b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/echo-response.xml @@ -0,0 +1,6 @@ + + + + Hello + + \ No newline at end of file diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/empty-soap.xml b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/empty-soap.xml new file mode 100755 index 00000000..18e3966d --- /dev/null +++ b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/empty-soap.xml @@ -0,0 +1,5 @@ + + + QQQ + + diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/encrypted-soap.xml b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/encrypted-soap.xml new file mode 100755 index 00000000..bb4c2dfc --- /dev/null +++ b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/encrypted-soap.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + CN=client,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown + + 1196367787 + + + + + + + CtuWCgHBFl1zHdRU70e4aPoRwpMH8GMzWKmywSxdrMz4dS+hOexUu8sY2SUl0jdO+IfL/oNYj4eic7G6utkddKeOCHM8Rj7gtr2Elol2q1ZHXc5N1DUk99t7dYs+XmMU2ULqyGh0T0rmhbABmbxZDB4NzwCBFvXxEUUFFBbN460= + + + + + + + + + + + + pqzkZJz0/RdUsf8y1z04PvWv48lYtrfmskeW8304PtXD2lXS2iGva5SlL7vc96aA6915zzyld4O0 + c+0ISwgvHnharmwatlih/kGfHNY0/LBDidzsbbjrsMWFiq3MLy3x9Sj79pHnChd2BSw9D49kgtUN + W8uIAUxLlsPqdSst9hEq/RYUk0M6mx8HBy2DnE3t36ifrB/1QnY4ohQoMrBARu4HIWidCxhajFSX + i1ZNX8ff4RrD3JUoAlDX6J5zj3+YfxtaaZhoMXRshxAhJwoLidKGlFKN6gsZXqorDJ8ETZ6SdCJ3 + O2f4c+WW1xQLUdvt + + + + + \ No newline at end of file diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/signed-soap.xml b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/signed-soap.xml new file mode 100755 index 00000000..92af9850 --- /dev/null +++ b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/signed-soap.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + I6X9SMGsJ5sOBL5NUM4H3KMTpc8= + + + + + DX4XwuzPSkwTXIPKTWKXXf05PWssJjkFUVxg8Joj4OmB6yqDzzTJ1GHRI/3PFZLjoUQq7dL4JqAL + BjcD2lb9ka0c/EILe59QtD1USz1jM8i/zAtNDv3J38yIIHeAp+v/zdT8cNAvG+unwpJvAYMaS34j + mziXbS3/NLMFkroYR3Q= + + + + + + CN=client,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown + + 1196367787 + + + + + + + + + + Hello + + \ No newline at end of file diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/support/merlin.properties b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/support/merlin.properties new file mode 100644 index 00000000..0c241d26 --- /dev/null +++ b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/support/merlin.properties @@ -0,0 +1,6 @@ +org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin +org.apache.ws.security.crypto.merlin.keystore.type=pkcs12 +org.apache.ws.security.crypto.merlin.keystore.password=security +org.apache.ws.security.crypto.merlin.keystore.alias=16c73ab6-b892-458f-abf5-2f875f74882e +org.apache.ws.security.crypto.merlin.alias.password=security +org.apache.ws.security.crypto.merlin.file=org/springframework/ws/soap/security/wss4j/support/x509.PFX.MSFT diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/support/x509.PFX.MSFT b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/support/x509.PFX.MSFT new file mode 100644 index 00000000..98be0dfc Binary files /dev/null and b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/support/x509.PFX.MSFT differ diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenDigest-soap.xml b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenDigest-soap.xml new file mode 100755 index 00000000..2a3b8a6f --- /dev/null +++ b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenDigest-soap.xml @@ -0,0 +1,25 @@ + + + + + Bert + kwNstEaiFOrI7B31j7GuETYvdgk= + + 9mdsYDCrjjYRur0rxzYt2oD7 + + 2006-06-01T23:48:42Z + + + + + + QQQ + + diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenPlainText-soap.xml b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenPlainText-soap.xml new file mode 100755 index 00000000..7e18e9dd --- /dev/null +++ b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenPlainText-soap.xml @@ -0,0 +1,20 @@ + + + + + Bert + Ernie + + + + + + QQQ + + \ No newline at end of file diff --git a/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenPlainTextWithHeaders-soap.xml b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenPlainTextWithHeaders-soap.xml new file mode 100755 index 00000000..766cb216 --- /dev/null +++ b/security/src/test/resources/org/springframework/ws/soap/security/wss4j/usernameTokenPlainTextWithHeaders-soap.xml @@ -0,0 +1,22 @@ + + + 1 + + + Bert + Ernie + + + + 2 + + + QQQ + + \ No newline at end of file diff --git a/security/src/test/resources/private.jks b/security/src/test/resources/private.jks new file mode 100755 index 00000000..67954873 Binary files /dev/null and b/security/src/test/resources/private.jks differ