From f6f11b09d19297e446b1e01ad096aa244bdb5dfc Mon Sep 17 00:00:00 2001 From: Jamin Hitchcock Date: Wed, 12 Aug 2015 21:21:16 -0500 Subject: [PATCH] SWS-886 Update to WSS4J 2.0.x / XMLSec 2.0.x --- .gitignore | 3 + build.gradle | 4 +- .../ws/soap/security/wss4j2/Wss4jHandler.java | 123 +++ .../wss4j2/Wss4jSecurityFaultException.java | 37 + .../wss4j2/Wss4jSecurityInterceptor.java | 784 ++++++++++++++++++ .../Wss4jSecuritySecurementException.java | 40 + .../Wss4jSecurityValidationException.java | 40 + .../AbstractWsPasswordCallbackHandler.java | 170 ++++ .../callback/KeyStoreCallbackHandler.java | 136 +++ ...mplePasswordValidationCallbackHandler.java | 70 ++ ...rityPasswordValidationCallbackHandler.java | 118 +++ .../UsernameTokenPrincipalCallback.java | 49 ++ .../security/wss4j2/callback/package.html | 5 + .../ws/soap/security/wss4j2/package.html | 6 + .../wss4j2/support/CryptoFactoryBean.java | 186 +++++ .../soap/security/wss4j2/support/package.html | 5 + ...ssageInterceptorUsernameTokenTestCase.java | 3 + .../wss4j2/AxiomWss4jInterceptorTest.java | 21 + ...Wss4jMessageInterceptorEncryptionTest.java | 21 + ...xiomWss4jMessageInterceptorHeaderTest.java | 21 + .../AxiomWss4jMessageInterceptorSignTest.java | 21 + ...Wss4jMessageInterceptorSoapActionTest.java | 21 + ...ptorSpringSecurityCallbackHandlerTest.java | 22 + ...mWss4jMessageInterceptorTimestampTest.java | 21 + ...InterceptorUsernameTokenSignatureTest.java | 22 + ...4jMessageInterceptorUsernameTokenTest.java | 21 + .../AxiomWss4jMessageInterceptorX509Test.java | 22 + .../wss4j2/SaajWss4jInterceptorTest.java | 21 + ...Wss4jMessageInterceptorEncryptionTest.java | 21 + ...SaajWss4jMessageInterceptorHeaderTest.java | 21 + .../SaajWss4jMessageInterceptorSignTest.java | 83 ++ ...Wss4jMessageInterceptorSoapActionTest.java | 21 + ...ptorSpringSecurityCallbackHandlerTest.java | 22 + ...jWss4jMessageInterceptorTimestampTest.java | 21 + ...InterceptorUsernameTokenSignatureTest.java | 22 + ...4jMessageInterceptorUsernameTokenTest.java | 21 + .../SaajWss4jMessageInterceptorX509Test.java | 22 + .../wss4j2/Wss4jInterceptorTestCase.java | 84 ++ ...jMessageInterceptorEncryptionTestCase.java | 86 ++ ...Wss4jMessageInterceptorHeaderTestCase.java | 160 ++++ .../Wss4jMessageInterceptorSignTestCase.java | 123 +++ ...jMessageInterceptorSoapActionTestCase.java | 107 +++ ...SpringSecurityCallbackHandlerTestCase.java | 126 +++ ...4jMessageInterceptorTimestampTestCase.java | 103 +++ ...rceptorUsernameTokenSignatureTestCase.java | 45 + ...ssageInterceptorUsernameTokenTestCase.java | 154 ++++ .../Wss4jMessageInterceptorX509TestCase.java | 72 ++ .../soap/security/wss4j2/Wss4jTestCase.java | 281 +++++++ .../callback/KeyStoreCallbackHandlerTest.java | 55 ++ ...PasswordValidationCallbackHandlerTest.java | 80 ++ .../wss4j2/support/CryptoFactoryBeanTest.java | 63 ++ .../security/wss4j2/customHeader-soap.xml | 9 + .../ws/soap/security/wss4j2/echo-response.xml | 6 + .../ws/soap/security/wss4j2/empty-soap.xml | 5 + .../ws/soap/security/wss4j2/empty-soap12.xml | 5 + .../wss4j2/emptySecurityHeader-soap.xml | 10 + .../soap/security/wss4j2/encrypted-soap.xml | 20 + .../security/wss4j2/expiredTimestamp-soap.xml | 14 + .../ws/soap/security/wss4j2/signed-soap.xml | 29 + .../wss4j2/usernameTokenDigest-soap.xml | 6 + .../wss4j2/usernameTokenPlainText-soap.xml | 19 + .../wss4j2/usernameTokenPlainText-soap12.xml | 19 + ...ernameTokenPlainTextQualifiedType-soap.xml | 17 + ...usernameTokenPlainTextWithHeaders-soap.xml | 21 + 64 files changed, 3985 insertions(+), 1 deletion(-) create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jHandler.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityFaultException.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecuritySecurementException.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityValidationException.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/AbstractWsPasswordCallbackHandler.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/KeyStoreCallbackHandler.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/SimplePasswordValidationCallbackHandler.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/SpringSecurityPasswordValidationCallbackHandler.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/UsernameTokenPrincipalCallback.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/package.html create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/package.html create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/support/CryptoFactoryBean.java create mode 100644 spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/support/package.html create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jInterceptorTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorEncryptionTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorHeaderTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSignTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSoapActionTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorTimestampTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorX509Test.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jInterceptorTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorEncryptionTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorHeaderTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSignTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSoapActionTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorTimestampTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorUsernameTokenSignatureTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorUsernameTokenTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorX509Test.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jInterceptorTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorEncryptionTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorHeaderTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSoapActionTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSpringSecurityCallbackHandlerTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorTimestampTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorUsernameTokenSignatureTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorUsernameTokenTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorX509TestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jTestCase.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/callback/KeyStoreCallbackHandlerTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/callback/SpringSecurityPasswordValidationCallbackHandlerTest.java create mode 100644 spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/support/CryptoFactoryBeanTest.java create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/customHeader-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/echo-response.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/empty-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/empty-soap12.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/emptySecurityHeader-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/encrypted-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/expiredTimestamp-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/signed-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenDigest-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainText-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainText-soap12.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainTextQualifiedType-soap.xml create mode 100644 spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainTextWithHeaders-soap.xml diff --git a/.gitignore b/.gitignore index 8fff39bf..bac1ee93 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ out .gradle _site +/.classpath +/.project +/.settings/ diff --git a/build.gradle b/build.gradle index 6ce4cebb..bf547ad8 100644 --- a/build.gradle +++ b/build.gradle @@ -250,7 +250,9 @@ project('spring-ws-security') { optional("com.sun.xml.wss:xws-security:3.0") { exclude group: 'javax.xml.crypto', module: 'xmldsig' } - optional("org.apache.ws.security:wss4j:1.6.15") + + compile("org.apache.ws.security:wss4j:1.6.19") + compile("org.apache.wss4j:wss4j-ws-security-dom:2.0.5") // SOAP provided("com.sun.xml.messaging.saaj:saaj-impl:1.3.19") // required for XWSS diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jHandler.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jHandler.java new file mode 100644 index 00000000..3955a93c --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jHandler.java @@ -0,0 +1,123 @@ +/* + * Copyright 2005-2012 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.wss4j2; + +import java.util.List; +import java.util.Properties; + +import org.springframework.ws.context.MessageContext; +import org.w3c.dom.Document; +import org.apache.wss4j.common.ConfigurationConstants; +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.ext.WSSecurityException; +import org.apache.wss4j.dom.WSSecurityEngineResult; +import org.apache.wss4j.dom.handler.HandlerAction; +import org.apache.wss4j.dom.handler.RequestData; +import org.apache.wss4j.dom.handler.WSHandler; + +/** + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @since 2.3.0 + */ +class Wss4jHandler extends WSHandler { + + /** Keys are constants from {@link WSHandlerConstants}; values are strings. */ + private Properties options = new Properties(); + + private String securementPassword; + + private Crypto securementEncryptionCrypto; + + private Crypto securementSignatureCrypto; + + Wss4jHandler() { + // set up default handler properties + options.setProperty(ConfigurationConstants.MUST_UNDERSTAND, Boolean.toString(true)); + options.setProperty(ConfigurationConstants.ENABLE_SIGNATURE_CONFIRMATION, Boolean.toString(true)); + } + + public void doSenderAction( + Document doc, + RequestData reqData, + List actions, + boolean isRequest) throws WSSecurityException + { + super.doSenderAction(doc, reqData, actions, isRequest); + } + + @Override + protected boolean checkReceiverResultsAnyOrder(List wsResult, List actions) { + return super.checkReceiverResultsAnyOrder(wsResult, actions); + } + + void setOption(String key, String value) { + options.setProperty(key, value); + } + + void setOption(String key, boolean value) { + options.setProperty(key, Boolean.toString(value)); + } + + @Override + public Object getOption(String key) { + return options.getProperty(key); + } + + void setSecurementPassword(String securementPassword) { + this.securementPassword = securementPassword; + } + + void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) { + this.securementEncryptionCrypto = securementEncryptionCrypto; + } + + void setSecurementSignatureCrypto(Crypto securementSignatureCrypto) { + this.securementSignatureCrypto = securementSignatureCrypto; + } + + @Override + public String getPassword(Object msgContext) { + return securementPassword; + } + + @Override + public Object getProperty(Object msgContext, String key) { + return ((MessageContext) msgContext).getProperty(key); + } + + @Override + protected Crypto loadEncryptionCrypto(RequestData reqData) throws WSSecurityException { + return securementEncryptionCrypto; + } + + @Override + public Crypto loadSignatureCrypto(RequestData reqData) throws WSSecurityException { + return securementSignatureCrypto; + } + + @Override + public void setPassword(Object msgContext, String password) { + securementPassword = password; + } + + @Override + public void setProperty(Object msgContext, String key, Object value) { + ((MessageContext) msgContext).setProperty(key, value); + } +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityFaultException.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityFaultException.java new file mode 100644 index 00000000..f0a873c5 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityFaultException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2014 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.wss4j2; + +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 + * @author Jamin Hitchcock + * @since 2.3.0 + */ +@SuppressWarnings("serial") +public class Wss4jSecurityFaultException extends WsSecurityFaultException { + + public Wss4jSecurityFaultException(QName faultCode, String faultString, String faultActor) { + super(faultCode, faultString, faultActor); + } +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java new file mode 100644 index 00000000..a3e573e1 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java @@ -0,0 +1,784 @@ +/* + * Copyright 2005-2014 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.wss4j2; + +import java.io.IOException; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.wss4j.common.ConfigurationConstants; +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.ext.WSSecurityException; +import org.apache.wss4j.common.principal.WSUsernameTokenPrincipalImpl; +import org.apache.wss4j.dom.WSConstants; +import org.apache.wss4j.dom.WSSConfig; +import org.apache.wss4j.dom.WSSecurityEngine; +import org.apache.wss4j.dom.WSSecurityEngineResult; +import org.apache.wss4j.dom.handler.HandlerAction; +import org.apache.wss4j.dom.handler.RequestData; +import org.apache.wss4j.dom.handler.WSHandlerConstants; +import org.apache.wss4j.dom.handler.WSHandlerResult; +import org.apache.wss4j.dom.message.token.Timestamp; +import org.apache.wss4j.dom.util.WSSecurityUtil; +import org.apache.wss4j.dom.validate.Credential; +import org.apache.wss4j.dom.validate.SignatureTrustValidator; +import org.apache.wss4j.dom.validate.TimestampValidator; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.AbstractWsSecurityInterceptor; +import org.springframework.ws.soap.security.WsSecuritySecurementException; +import org.springframework.ws.soap.security.WsSecurityValidationException; +import org.springframework.ws.soap.security.callback.CallbackHandlerChain; +import org.springframework.ws.soap.security.callback.CleanupCallback; +import org.springframework.ws.soap.security.wss4j2.callback.UsernameTokenPrincipalCallback; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * A WS-Security endpoint interceptor based on Apache's WSS4J. This interceptor supports messages created by the {@link + * org.springframework.ws.soap.axiom.AxiomSoapMessageFactory} and the {@link org.springframework.ws.soap.saaj.SaajSoapMessageFactory}. + * + *

The validation and securement actions executed by this interceptor are configured via {@code validationActions} + * and {@code securementActions} properties, respectively. Actions should be passed as a space-separated strings. + * + *

Valid validation actions are: + * + *

+ * + * + * + * + * + * + * + *
Validation actionDescription
{@code UsernameToken}Validates username token
{@code Timestamp}Validates the timestamp
{@code Encrypt}Decrypts the message
{@code Signature}Validates the signature
{@code NoSecurity}No action performed
+ *

+ * Securement actions are: + * + *

+ * + * + * + * + * + * + * + * + *
Securement actionDescription
{@code UsernameToken}Adds a username token
{@code UsernameTokenSignature}Adds a username token and a signature username token secret key
{@code Timestamp}Adds a timestamp
{@code Encrypt}Encrypts the response
{@code Signature}Signs the response
{@code NoSecurity}No action performed
+ * + *

The order of the actions that the client performed to secure the messages is significant and is enforced by the + * interceptor. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @author Greg Turnquist + * @author Jamin Hitchcock + * @see Apache WSS4J 2.0 + * @since 2.3.0 + */ +public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor implements InitializingBean { + + public static final String SECUREMENT_USER_PROPERTY_NAME = "Wss4jSecurityInterceptor.securementUser"; + + private String securementActions; + + private String securementUsername; + + private CallbackHandler validationCallbackHandler; + + private String validationActions; + + private List validationActionsVector; + + private String validationActor; + + private Crypto validationDecryptionCrypto; + + private Crypto validationSignatureCrypto; + + private boolean timestampStrict = true; + + private boolean enableSignatureConfirmation; + + private int validationTimeToLive = 300; + + private int securementTimeToLive = 300; + + private int futureTimeToLive = 60; + + private WSSConfig wssConfig; + + private final Wss4jHandler handler = new Wss4jHandler(); + + private final WSSecurityEngine securityEngine = new WSSecurityEngine(); + + private boolean enableRevocation; + + private boolean bspCompliant; + + private boolean securementUseDerivedKey; + + // Allow RSA 15 to maintain default behavior + private boolean allowRSA15KeyTransportAlgorithm = true; + + // To maintain same behavior as default, this flag is set to true + private boolean removeSecurityHeader = true; + + public void setSecurementActions(String securementActions) { + this.securementActions = securementActions; + } + + /** + * The actor name of the {@code 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 setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) { + handler.setSecurementEncryptionCrypto(securementEncryptionCrypto); + } + + /** + * Defines which key identifier type to use. The WS-Security specifications recommends to use the identifier type + * {@code IssuerSerial}. For possible encryption key identifier types refer to {@link + * org.apache.ws.security.handler.WSHandlerConstants#keyIdentifier}. For encryption {@code IssuerSerial}, + * {@code X509KeyIdentifier}, {@code DirectReference}, {@code Thumbprint}, + * {@code SKIKeyIdentifier}, and {@code 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} and {@link WSConstants#KEYTRANSPORT_RSAOEP}. + */ + public void setSecurementEncryptionKeyTransportAlgorithm(String securementEncryptionKeyTransportAlgorithm) { + handler.setOption(WSHandlerConstants.ENC_KEY_TRANSPORT, securementEncryptionKeyTransportAlgorithm); + } + + /** + * Property to define which parts of the request shall be encrypted. + * + *

The value of this property is a list of semicolon 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 precede + * each element name. + * + *

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

+	 * <property name="securementEncryptionParts"
+	 *	 value="{Content}{http://example.org/paymentv2}CreditCard;
+	 *			   {Element}{}UserName" />
+	 * 
+ * The the first entry of the list identifies the element {@code CreditCard} in the namespace + * {@code 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 omitted. In this case the encryption mode defaults to + * {@code Content} and the namespace is set to the SOAP namespace. + * + *

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

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

If no list is specified, the handler encrypts the SOAP Body in {@code 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 certificate in the configuration file is not a security risk, because the + * public key of that certificate is used only. + */ + 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. + * @see WSConstants#RSA + * @see WSConstants#DSA + */ + public void setSecurementSignatureAlgorithm(String securementSignatureAlgorithm) { + handler.setOption(WSHandlerConstants.SIG_ALGO, securementSignatureAlgorithm); + } + + /** + * Defines which signature digest algorithm to use. + */ + public void setSecurementSignatureDigestAlgorithm(String digestAlgorithm) { + handler.setOption(WSHandlerConstants.SIG_DIGEST_ALGO, digestAlgorithm); + } + + 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 + * {@code IssuerSerial}. For possible signature key identifier types refer to {@link + * org.apache.ws.security.handler.WSHandlerConstants#keyIdentifier}. For signature {@code IssuerSerial} and + * {@code DirectReference} are valid only. + */ + public void setSecurementSignatureKeyIdentifier(String securementSignatureKeyIdentifier) { + handler.setOption(WSHandlerConstants.SIG_KEY_ID, securementSignatureKeyIdentifier); + } + + /** + * Property 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 property 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 {@code Token} signs the token and takes care of the + * different formats. + * + *

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

+	 * <property name="securementSignatureParts"
+	 *	 value="{}{http://schemas.xmlsoap.org/soap/envelope/}Body; Token" />
+	 * 
+ * To specify an element without a namespace use the string {@code 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 {@code Body} then the SOAP namespace + * identifier can be empty ({@code {}}). + */ + public void setSecurementSignatureParts(String securementSignatureParts) { + handler.setOption(WSHandlerConstants.SIGNATURE_PARTS, securementSignatureParts); + } + + /** + * The user's name for signature. + * + *

This name is used as the alias name in the keystore to get user's + * certificate and private key to perform signing. + * + *

If this parameter is not set, then the signature + * function falls back to the alias specified by {@link #setSecurementUsername(String)}. + * + */ + public void setSecurementSignatureUser(String securementSignatureUser) { + handler.setOption(WSHandlerConstants.SIGNATURE_USER, securementSignatureUser); + } + + /** Sets the username for securement username token or/and the alias of the private key for securement signature */ + public void setSecurementUsername(String securementUsername) { + this.securementUsername = securementUsername; + } + + /** Sets the time to live on the outgoing message */ + public void setSecurementTimeToLive(int securementTimeToLive) { + if (securementTimeToLive <= 0) { + throw new IllegalArgumentException("timeToLive must be positive"); + } + this.securementTimeToLive = securementTimeToLive; + } + + /** + * Enables the derivation of keys as per the UsernameTokenProfile 1.1 spec. Default is {@code true}. + */ + public void setSecurementUseDerivedKey(boolean securementUseDerivedKey) { + this.securementUseDerivedKey = securementUseDerivedKey; + } + + /** Sets the server-side time to live */ + public void setValidationTimeToLive(int validationTimeToLive) { + if (validationTimeToLive <= 0) { + throw new IllegalArgumentException("timeToLive must be positive"); + } + this.validationTimeToLive = validationTimeToLive; + } + + /** Sets the validation actions to be executed by the interceptor. */ + public void setValidationActions(String actions) { + this.validationActions = actions; + try { + validationActionsVector = WSSecurityUtil.decodeAction(actions); + } + catch (WSSecurityException ex) { + throw new IllegalArgumentException(ex); + } + } + + public void setValidationActor(String validationActor) { + this.validationActor = validationActor; + } + + /** + * Sets the {@link org.apache.ws.security.WSPasswordCallback} handler to use when validating messages. + * + * @see #setValidationCallbackHandlers(CallbackHandler[]) + */ + public void setValidationCallbackHandler(CallbackHandler callbackHandler) { + this.validationCallbackHandler = callbackHandler; + } + + /** + * Sets the {@link org.apache.ws.security.WSPasswordCallback} handlers to use when validating messages. + * + * @see #setValidationCallbackHandler(CallbackHandler) + */ + public void setValidationCallbackHandlers(CallbackHandler[] callbackHandler) { + this.validationCallbackHandler = new CallbackHandlerChain(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 {@code mustUnderstand} attribute on WS-Security headers on outgoing messages. Default is + * {@code true}. + */ + public void setSecurementMustUnderstand(boolean securementMustUnderstand) { + handler.setOption(WSHandlerConstants.MUST_UNDERSTAND, securementMustUnderstand); + } + + /** + * Sets whether or not a {@code Nonce} element is added to the + * {@code UsernameToken}s. Default is {@code false}. + */ + public void setSecurementUsernameTokenNonce(boolean securementUsernameTokenNonce) { + handler.setOption(ConfigurationConstants.ADD_USERNAMETOKEN_NONCE, securementUsernameTokenNonce); + } + + /** + * Sets whether or not a {@code Created} element is added to the + * {@code UsernameToken}s. Default is {@code false}. + */ + public void setSecurementUsernameTokenCreated(boolean securementUsernameTokenCreated) + { + handler.setOption(ConfigurationConstants.ADD_USERNAMETOKEN_CREATED, securementUsernameTokenCreated); + } + + /** + * Sets the web service specification settings. + *

+ * The default settings follow the latest OASIS and changing anything might violate the OASIS specs. + * + * @param config web service security configuration or {@code null} to use default settings + */ + public void setWssConfig(WSSConfig config) { + securityEngine.setWssConfig(config); + wssConfig = config; + } + + /** + * Set whether to enable CRL checking or not when verifying trust in a certificate. + */ + public void setEnableRevocation(boolean enableRevocation) { + this.enableRevocation = enableRevocation; + } + + /** + * Set the WS-I Basic Security Profile compliance mode. Default is {@code true}. + */ + public void setBspCompliant(boolean bspCompliant) { + this.handler.setOption(WSHandlerConstants.IS_BSP_COMPLIANT, bspCompliant); + this.bspCompliant = bspCompliant; + } + + /** + * Sets whether the RSA 1.5 key transport algorithm is allowed. + */ + public void setAllowRSA15KeyTransportAlgorithm(boolean allow) + { + this.allowRSA15KeyTransportAlgorithm = allow; + } + + /** + * Sets the time in seconds in the future within which the Created time of an + * incoming Timestamp is valid. The default is 60 seconds. + */ + public void setFutureTimeToLive(int futureTimeToLive) { + if (futureTimeToLive <= 0) { + throw new IllegalArgumentException("futureTimeToLive must be positive"); + } + this.futureTimeToLive = futureTimeToLive; + } + + public boolean getRemoveSecurityHeader() { + return removeSecurityHeader; + } + + public void setRemoveSecurityHeader(boolean removeSecurityHeader) { + this.removeSecurityHeader = removeSecurityHeader; + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.isTrue(validationActions != null || securementActions != null, + "validationActions or securementActions are required"); + if (validationActions != null) { + if (validationActionsVector.contains(WSConstants.UT)) { + Assert.notNull(validationCallbackHandler, "validationCallbackHandler is required"); + } + + if (validationActionsVector.contains(WSConstants.SIGN)) { + Assert.notNull(validationSignatureCrypto, "validationSignatureCrypto is required"); + } + } + // securement actions are not to be validated at start up as they could + // be configured dynamically via the message context + + // allow for qualified password types for .Net interoperability + securityEngine.getWssConfig().setAllowNamespaceQualifiedPasswordTypes(true); + } + + @Override + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecuritySecurementException { + + List securementActionsVector = new ArrayList(); + try { + securementActionsVector = WSSecurityUtil.decodeHandlerAction(securementActions, wssConfig); + } + catch (WSSecurityException ex) { + throw new Wss4jSecuritySecurementException(ex.getMessage(), ex); + } + + if (securementActionsVector.isEmpty() && !enableSignatureConfirmation) { + return; + } + if (logger.isDebugEnabled()) { + logger.debug("Securing message [" + soapMessage + "] with actions [" + securementActions + "]"); + } + RequestData requestData = initializeRequestData(messageContext); + + Document envelopeAsDocument = soapMessage.getDocument(); + try { + handler.doSenderAction(envelopeAsDocument, requestData, securementActionsVector, false); + } + catch (WSSecurityException ex) { + throw new Wss4jSecuritySecurementException(ex.getMessage(), ex); + } + + soapMessage.setDocument(envelopeAsDocument); + } + + /** + * Creates and initializes a request data for the given message context. + * + * @param messageContext the message context + * @return the request data + */ + protected RequestData initializeRequestData(MessageContext messageContext) { + RequestData requestData = new RequestData(); + requestData.setMsgContext(messageContext); + + // reads securementUsername first from the context then from the property + String contextUsername = (String) messageContext.getProperty(SECUREMENT_USER_PROPERTY_NAME); + if (StringUtils.hasLength(contextUsername)) { + requestData.setUsername(contextUsername); + } + else { + requestData.setUsername(securementUsername); + } + + requestData.setTimeToLive(securementTimeToLive); + + requestData.setUseDerivedKeyForMAC(securementUseDerivedKey); + + requestData.setWssConfig(wssConfig); + + messageContext.setProperty(WSHandlerConstants.TTL_TIMESTAMP, Integer.toString(securementTimeToLive)); + + return requestData; + } + + /** + * Creates and initializes a request data for the given message context. + * + * @param messageContext the message context + * @return the request data + */ + protected RequestData initializeValidationRequestData(MessageContext messageContext) { + RequestData requestData = new RequestData(); + requestData.setMsgContext(messageContext); + + requestData.setWssConfig(wssConfig); + + requestData.setDecCrypto(validationDecryptionCrypto); + + requestData.setSigVerCrypto(validationSignatureCrypto); + + requestData.setCallbackHandler(validationCallbackHandler); + + messageContext.setProperty(WSHandlerConstants.TTL_TIMESTAMP, Integer.toString(validationTimeToLive)); + + requestData.setAllowRSA15KeyTransportAlgorithm(allowRSA15KeyTransportAlgorithm); + + requestData.setDisableBSPEnforcement(!bspCompliant); + if (requestData.getBSPEnforcer() != null) + { + requestData.getBSPEnforcer().setDisableBSPRules(!bspCompliant); + } + + return requestData; + } + + @Override + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { + if (logger.isDebugEnabled()) { + logger.debug("Validating message [" + soapMessage + "] with actions [" + validationActions + "]"); + } + + if (validationActionsVector.contains(WSConstants.NO_SECURITY)) { + return; + } + + Document envelopeAsDocument = soapMessage.getDocument(); + + // Header processing + + try { + RequestData validationData = initializeValidationRequestData(messageContext); + + String actor = validationActor; + if (actor == null) { + actor = ""; + } + + Element elem = WSSecurityUtil.getSecurityHeader(envelopeAsDocument, actor); + List results = securityEngine + .processSecurityHeader(elem, validationData); + + // Results verification + if (CollectionUtils.isEmpty(results)) { + throw new Wss4jSecurityValidationException("No WS-Security header found"); + } + + checkResults(results, validationActionsVector); + + // puts the results in the context + // useful for Signature Confirmation + updateContextWithResults(messageContext, results); + + verifyCertificateTrust(results); + + verifyTimestamp(results); + + processPrincipal(results); + } + catch (WSSecurityException ex) { + throw new Wss4jSecurityValidationException(ex.getMessage(), ex); + } + + soapMessage.setDocument(envelopeAsDocument); + + if (this.getRemoveSecurityHeader()) { + soapMessage.getEnvelope().getHeader().removeHeaderElement(WS_SECURITY_NAME); + } + } + + /** + * Checks whether the received headers match the configured validation actions. Subclasses could override this method + * for custom verification behavior. + * + * + * @param results the results of the validation function + * @param validationActions the decoded validation actions + * @throws Wss4jSecurityValidationException if the results are deemed invalid + */ + protected void checkResults(List results, List validationActions) + throws Wss4jSecurityValidationException { + if (!handler.checkReceiverResultsAnyOrder(results, validationActions)) { + throw new Wss4jSecurityValidationException("Security processing failed (actions mismatch)"); + } + } + + /** + * Puts the results of WS-Security headers processing in the message context. Some actions like Signature + * Confirmation require this. + */ + @SuppressWarnings("unchecked") + private void updateContextWithResults(MessageContext messageContext, List results) { + List handlerResults; + if ((handlerResults = (List) messageContext.getProperty(WSHandlerConstants.RECV_RESULTS)) == null) { + handlerResults = new ArrayList(); + messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults); + } + WSHandlerResult rResult = new WSHandlerResult(validationActor, results); + handlerResults.add(0, rResult); + messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults); + } + + /** Verifies the trust of a certificate. */ + protected void verifyCertificateTrust(List results) throws WSSecurityException { + WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.SIGN); + + if (actionResult != null) { + X509Certificate returnCert = + (X509Certificate) actionResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE); + Credential credential = new Credential(); + credential.setCertificates(new X509Certificate[] { returnCert}); + + RequestData requestData = new RequestData(); + requestData.setSigVerCrypto(validationSignatureCrypto); + requestData.setEnableRevocation(enableRevocation); + + SignatureTrustValidator validator = new SignatureTrustValidator(); + validator.validate(credential, requestData); + } + } + + /** Verifies the timestamp. */ + protected void verifyTimestamp(List results) throws WSSecurityException { + WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.TS); + + if (actionResult != null) { + Timestamp timestamp = (Timestamp) actionResult.get(WSSecurityEngineResult.TAG_TIMESTAMP); + if (timestamp != null && timestampStrict) { + Credential credential = new Credential(); + credential.setTimestamp(timestamp); + + RequestData requestData = new RequestData(); + WSSConfig config = WSSConfig.getNewInstance(); + config.setTimeStampTTL(validationTimeToLive); + config.setTimeStampStrict(timestampStrict); + config.setTimeStampFutureTTL(futureTimeToLive); + requestData.setWssConfig(config); + + TimestampValidator validator = new TimestampValidator(); + validator.validate(credential, requestData); + } + } + } + + private void processPrincipal(List results) { + WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.UT); + + if (actionResult != null) { + Principal principal = (Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL); + if (principal != null && principal instanceof WSUsernameTokenPrincipalImpl) { + WSUsernameTokenPrincipalImpl usernameTokenPrincipal = (WSUsernameTokenPrincipalImpl) principal; + UsernameTokenPrincipalCallback callback = new UsernameTokenPrincipalCallback(usernameTokenPrincipal); + try { + validationCallbackHandler.handle(new Callback[]{callback}); + } + catch (IOException ex) { + logger.warn("Principal callback resulted in IOException", ex); + } + catch (UnsupportedCallbackException ex) { + // ignore + } + } + } + } + + @Override + protected void cleanUp() { + if (validationCallbackHandler != null) { + try { + CleanupCallback cleanupCallback = new CleanupCallback(); + validationCallbackHandler.handle(new Callback[]{cleanupCallback}); + } + catch (IOException ex) { + logger.warn("Cleanup callback resulted in IOException", ex); + } + catch (UnsupportedCallbackException ex) { + // ignore + } + } + } +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecuritySecurementException.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecuritySecurementException.java new file mode 100644 index 00000000..02013862 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecuritySecurementException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2014 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.wss4j2; + +import org.springframework.ws.soap.security.WsSecuritySecurementException; + +/** + * WSS4J-specific version of the {@link WsSecuritySecurementException}. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @since 2.3.0 + */ +@SuppressWarnings("serial") +public class Wss4jSecuritySecurementException extends WsSecuritySecurementException { + + public Wss4jSecuritySecurementException(String msg) { + super(msg); + } + + public Wss4jSecuritySecurementException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityValidationException.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityValidationException.java new file mode 100644 index 00000000..30c4d8d5 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityValidationException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2014 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.wss4j2; + +import org.springframework.ws.soap.security.WsSecurityValidationException; + +/** + * WSS4J-specific version of the {@link WsSecurityValidationException}. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @since 2.3.0 + */ +@SuppressWarnings("serial") +public class Wss4jSecurityValidationException extends WsSecurityValidationException { + + public Wss4jSecurityValidationException(String msg) { + super(msg); + } + + public Wss4jSecurityValidationException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/AbstractWsPasswordCallbackHandler.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/AbstractWsPasswordCallbackHandler.java new file mode 100644 index 00000000..a6bc748e --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/AbstractWsPasswordCallbackHandler.java @@ -0,0 +1,170 @@ +/* + * Copyright 2005-2012 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.wss4j2.callback; + +import java.io.IOException; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.wss4j.common.ext.WSPasswordCallback; +import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.callback.CleanupCallback; + +/** + * Abstract base class for {@link javax.security.auth.callback.CallbackHandler} implementations that handle {@link + * WSPasswordCallback} callbacks. + * + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @since 2.3.0 + */ +public abstract class AbstractWsPasswordCallbackHandler extends AbstractCallbackHandler { + + /** + * Handles {@link WSPasswordCallback} callbacks. Inspects the callback {@link WSPasswordCallback#getUsage() usage} + * code, and calls the various {@code handle*} template methods. + * + * @param callback the callback + * @throws IOException in case of I/O errors + * @throws UnsupportedCallbackException when the callback is not supported + */ + @Override + protected final void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof WSPasswordCallback) { + WSPasswordCallback passwordCallback = (WSPasswordCallback) callback; + switch (passwordCallback.getUsage()) { + case WSPasswordCallback.DECRYPT: + handleDecrypt(passwordCallback); + break; + case WSPasswordCallback.USERNAME_TOKEN: + handleUsernameToken(passwordCallback); + break; + case WSPasswordCallback.SIGNATURE: + handleSignature(passwordCallback); + break; + case WSPasswordCallback.SECURITY_CONTEXT_TOKEN: + handleSecurityContextToken(passwordCallback); + break; + case WSPasswordCallback.CUSTOM_TOKEN: + handleCustomToken(passwordCallback); + break; + case WSPasswordCallback.SECRET_KEY: + handleSecretKey(passwordCallback); + break; + default: + throw new UnsupportedCallbackException(callback, + "Unknown usage [" + passwordCallback.getUsage() + "]"); + } + } + else if (callback instanceof CleanupCallback) { + handleCleanup((CleanupCallback) callback); + } + else if (callback instanceof UsernameTokenPrincipalCallback) { + handleUsernameTokenPrincipal((UsernameTokenPrincipalCallback) callback); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Invoked when the callback has a {@link WSPasswordCallback#DECRYPT} usage. + * + *

This method is invoked when WSS4J needs a password to get the private key of the {@link + * WSPasswordCallback#getIdentifier() identifier} (username) from the keystore. WSS4J uses this private key to + * decrypt the session (symmetric) key. Because the encryption method uses the public key to encrypt the session key + * it needs no password (a public key is usually not protected by a password). + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleDecrypt(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Invoked when the callback has a {@link WSPasswordCallback#USERNAME_TOKEN} usage. + * + *

This method is invoked when WSS4J needs the password to fill in or to verify a UsernameToken. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleUsernameToken(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Invoked when the callback has a {@link WSPasswordCallback#SIGNATURE} usage. + * + *

This method is invoked when WSS4J needs the password to get the private key of the {@link + * WSPasswordCallback#getIdentifier() identifier} (username) from the keystore. WSS4J uses this private key to + * produce a signature. The signature verfication uses the public key to verfiy the signature. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleSignature(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Invoked when the callback has a {@link WSPasswordCallback#SECURITY_CONTEXT_TOKEN} usage. + * + *

This method is invoked when WSS4J needs the key to to be associated with a SecurityContextToken. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleSecurityContextToken(WSPasswordCallback callback) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Invoked when the callback has a {@link WSPasswordCallback#CUSTOM_TOKEN} usage. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleCustomToken(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Invoked when the callback has a {@link WSPasswordCallback#SECRET_KEY} usage. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleSecretKey(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Invoked when a {@link CleanupCallback} is passed to {@link #handle(Callback[])}. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleCleanup(CleanupCallback callback) throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Invoked when a {@link UsernameTokenPrincipalCallback} is passed to {@link #handle(Callback[])}. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleUsernameTokenPrincipal(UsernameTokenPrincipalCallback callback) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/KeyStoreCallbackHandler.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/KeyStoreCallbackHandler.java new file mode 100644 index 00000000..27d1e701 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/KeyStoreCallbackHandler.java @@ -0,0 +1,136 @@ +/* + * Copyright 2005-2014 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.wss4j2.callback; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.Key; + +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.wss4j.common.ext.WSPasswordCallback; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.ws.soap.security.support.KeyStoreUtils; + +/** + * Callback handler that uses Java Security {@code KeyStore}s to handle cryptographic callbacks. Allows for + * specific key stores to be set for various cryptographic operations. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean + * @since 2.3.0 + */ +public class KeyStoreCallbackHandler extends AbstractWsPasswordCallbackHandler implements InitializingBean { + + private String privateKeyPassword; + + private char[] symmetricKeyPassword; + + private KeyStore keyStore; + + /** + * Invoked when the callback has a {@link WSPasswordCallback#DECRYPT} usage. + * + *

This method is invoked when WSS4J needs a password to get the private key of the {@link + * WSPasswordCallback#getIdentifier() identifier} (username) from the keystore. WSS4J uses this private key to + * decrypt the session (symmetric) key. Because the encryption method uses the public key to encrypt the session key + * it needs no password (a public key is usually not protected by a password). + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleDecrypt(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException { + callback.setPassword(privateKeyPassword); + } + + /** + * Invoked when the callback has a {@link WSPasswordCallback#SECRET_KEY} usage. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleSecretKey(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException { + String id = callback.getIdentifier(); + Key key; + + try { + key = keyStore.getKey(id, symmetricKeyPassword != null ? symmetricKeyPassword : privateKeyPassword.toCharArray()); + } catch (UnrecoverableKeyException e) { + throw new IOException("Could not get key", e); + } catch (KeyStoreException e) { + throw new IOException("Could not get key", e); + } catch (NoSuchAlgorithmException e) { + throw new IOException("Could not get key", e); + } + + callback.setKey(key.getEncoded()); + } + + /** Sets the key store to use if a symmetric key name is embedded. */ + public void setKeyStore(KeyStore keyStore) { + this.keyStore = keyStore; + } + + /** + * Sets the password used to retrieve private keys from the keystore. This property is required for decryption based + * on private keys, and signing. + */ + public void setPrivateKeyPassword(String privateKeyPassword) { + if (privateKeyPassword != null) { + this.privateKeyPassword = privateKeyPassword; + } + } + + /** + * Sets the password used to retrieve keys from the symmetric keystore. If this property is not set, it defaults to + * the private key password. + * + * @see #setPrivateKeyPassword(String) + */ + public void setSymmetricKeyPassword(String symmetricKeyPassword) { + if (symmetricKeyPassword != null) { + this.symmetricKeyPassword = symmetricKeyPassword.toCharArray(); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + if (keyStore == null) { + loadDefaultKeyStore(); + } + if (symmetricKeyPassword == null) { + symmetricKeyPassword = privateKeyPassword.toCharArray(); + } + } + + /** Loads the key store indicated by system properties. Delegates to {@link KeyStoreUtils#loadDefaultKeyStore()}. */ + protected void loadDefaultKeyStore() { + try { + keyStore = KeyStoreUtils.loadDefaultKeyStore(); + if (logger.isDebugEnabled()) { + logger.debug("Loaded default key store"); + } + } + catch (Exception ex) { + logger.warn("Could not open default key store", ex); + } + } + +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/SimplePasswordValidationCallbackHandler.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/SimplePasswordValidationCallbackHandler.java new file mode 100644 index 00000000..c0703a86 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/SimplePasswordValidationCallbackHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2005-2014 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.wss4j2.callback; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.wss4j.common.ext.WSPasswordCallback; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * Simple callback handler that validates passwords against a in-memory {@code Properties} object. Password + * validation is done on a case-sensitive basis. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @see #setUsers(java.util.Properties) + * @since 2.3.0 + */ +public class SimplePasswordValidationCallbackHandler extends AbstractWsPasswordCallbackHandler + implements InitializingBean { + + private Map users = new HashMap(); + + /** Sets the users to validate against. Property names are usernames, property values are passwords. */ + public void setUsers(Properties users) { + for (Map.Entry entry : users.entrySet()) { + if (entry.getKey() instanceof String && entry.getValue() instanceof String) { + this.users.put((String) entry.getKey(), (String) entry.getValue()); + } + } + } + + public void setUsersMap(Map users) { + this.users = users; + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(users, "users is required"); + } + + @Override + public void handleUsernameToken(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException + { + String username = callback.getIdentifier(); + String passwd = users.get(username); + callback.setPassword(passwd); + } +} \ No newline at end of file diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/SpringSecurityPasswordValidationCallbackHandler.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/SpringSecurityPasswordValidationCallbackHandler.java new file mode 100644 index 00000000..4961e8b2 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/SpringSecurityPasswordValidationCallbackHandler.java @@ -0,0 +1,118 @@ +/* + * Copyright 2005-2014 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.wss4j2.callback; + +import java.io.IOException; + +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.wss4j.common.ext.WSPasswordCallback; +import org.apache.wss4j.common.principal.WSUsernameTokenPrincipalImpl; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserCache; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.core.userdetails.cache.NullUserCache; +import org.springframework.util.Assert; +import org.springframework.ws.soap.security.callback.CleanupCallback; + +/** + * Callback handler that validates a plain text or digest password using an Spring Security {@code UserDetailsService}. + * + *

An Spring Security {@link UserDetailsService} is used to load {@link UserDetails} from. The digest of the + * password contained in this details object is then compared with the digest in the message. + * + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @since 2.3.0 + */ +public class SpringSecurityPasswordValidationCallbackHandler extends AbstractWsPasswordCallbackHandler + implements InitializingBean { + + private UserCache userCache = new NullUserCache(); + + private UserDetailsService userDetailsService; + + /** Sets the users cache. Not required, but can benefit performance. */ + public void setUserCache(UserCache userCache) { + this.userCache = userCache; + } + + /** Sets the Spring Security user details service. Required. */ + public void setUserDetailsService(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(userDetailsService, "userDetailsService is required"); + } + + + + /** + * Invoked when the callback has a {@link WSPasswordCallback#USERNAME_TOKEN} usage. + * + *

This method is invoked when WSS4J needs the password to fill in or to verify a UsernameToken. + * + *

Default implementation throws an {@link UnsupportedCallbackException}. + */ + protected void handleUsernameToken(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException { + UserDetails details = loadUserDetails(callback.getIdentifier()); + callback.setPassword(details.getPassword()); + } + + @Override + protected void handleUsernameTokenPrincipal(UsernameTokenPrincipalCallback callback) + throws IOException, UnsupportedCallbackException { + UserDetails user = loadUserDetails(callback.getPrincipal().getName()); + WSUsernameTokenPrincipalImpl principal = callback.getPrincipal(); + UsernamePasswordAuthenticationToken authRequest = + new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), user.getAuthorities()); + if (logger.isDebugEnabled()) { + logger.debug("Authentication success: " + authRequest.toString()); + } + SecurityContextHolder.getContext().setAuthentication(authRequest); + } + + @Override + protected void handleCleanup(CleanupCallback callback) throws IOException, UnsupportedCallbackException { + SecurityContextHolder.clearContext(); + } + + 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/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/UsernameTokenPrincipalCallback.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/UsernameTokenPrincipalCallback.java new file mode 100644 index 00000000..c8b43b6e --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/UsernameTokenPrincipalCallback.java @@ -0,0 +1,49 @@ +/* + * 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.wss4j2.callback; + +import java.io.Serializable; +import javax.security.auth.callback.Callback; + +import org.apache.wss4j.common.principal.WSUsernameTokenPrincipalImpl; + +/** + * Underlying security services instantiate and pass a {@code UsernameTokenPrincipalCallback} to the + * {@code handle} method of a {@code CallbackHandler} to pass a security + * {@code WSUsernameTokenPrincipal}. + * + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @see WSUsernameTokenPrincipalImpl + * @since 2.3.0 + */ +public class UsernameTokenPrincipalCallback implements Callback, Serializable { + + private static final long serialVersionUID = -3022202225157082715L; + + private final WSUsernameTokenPrincipalImpl principal; + + /** Construct a {@code UsernameTokenPrincipalCallback}. */ + public UsernameTokenPrincipalCallback(WSUsernameTokenPrincipalImpl principal) { + this.principal = principal; + } + + /** Get the retrieved {@code Principal}. */ + public WSUsernameTokenPrincipalImpl getPrincipal() { + return principal; + } +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/package.html b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/package.html new file mode 100644 index 00000000..8335862b --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/callback/package.html @@ -0,0 +1,5 @@ + + +Contains CallbackHandler implementations for WSS4J 2.0. + + \ No newline at end of file diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/package.html b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/package.html new file mode 100644 index 00000000..92fc4969 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/package.html @@ -0,0 +1,6 @@ + + +Contains classes for using the Apache WSS4J 2.0 WS-Security implementation within +Spring-WS. + + \ No newline at end of file diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/support/CryptoFactoryBean.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/support/CryptoFactoryBean.java new file mode 100644 index 00000000..c701599f --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/support/CryptoFactoryBean.java @@ -0,0 +1,186 @@ +/* + * Copyright 2005-2014 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.wss4j2.support; + +import java.io.IOException; +import java.util.Properties; + +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.CryptoFactory; +import org.apache.wss4j.common.crypto.Merlin; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +/** + * Spring factory bean for a WSS4J {@link Crypto}. Allows for strong-typed property configuration, or configuration + * through {@link Properties}. + * + *

Requires either individual properties, or the {@link #setConfiguration(java.util.Properties) configuration} property + * to be set. + * + * @author Tareq Abed Rabbo + * @author Arjen Poutsma + * @author Jamin Hitchcock + * @see org.apache.ws.security.components.crypto.Crypto + * @since 2.3.0 + */ +public class CryptoFactoryBean implements FactoryBean, InitializingBean { + + private Properties configuration = new Properties(); + + private Crypto crypto; + + private static final String CRYPTO_PROVIDER_PROPERTY = "org.apache.wss4j.crypto.provider"; + + /** + * Sets the configuration of the Crypto. Setting this property overrides all previously set configuration, through + * the type-safe properties + * + * @see org.apache.ws.security.components.crypto.CryptoFactory#getInstance(java.util.Properties) + */ + public void setConfiguration(Properties properties) { + Assert.notNull(properties, "'properties' must not be null"); + this.configuration.putAll(properties); + } + + /** + * Sets the {@link org.apache.ws.security.components.crypto.Crypto} provider name. Defaults to {@link + * org.apache.ws.security.components.crypto.Merlin}. + * + *

This property maps to the WSS4J {@code org.apache.ws.security.crypto.provider} property. + * + * @param cryptoProviderClass the crypto provider class + */ + public void setCryptoProvider(Class cryptoProviderClass) { + this.configuration.setProperty(CRYPTO_PROVIDER_PROPERTY, cryptoProviderClass.getName()); + } + + /** + * Sets the location of the key store to be loaded in the {@link org.apache.ws.security.components.crypto.Crypto} + * instance. + * + *

This property maps to the WSS4J {@code org.apache.ws.security.crypto.merlin.file} property. + * + * @param location the key store location + * @throws java.io.IOException when the resource cannot be opened + */ + public void setKeyStoreLocation(Resource location) throws IOException { + String resourcePath = getResourcePath(location); + this.configuration.setProperty("org.apache.ws.security.crypto.merlin.file", resourcePath); + } + + private String getResourcePath(Resource resource) throws IOException { + try { + return resource.getFile().getAbsolutePath(); + } + catch (IOException ex) { + if (resource instanceof ClassPathResource) { + ClassPathResource classPathResource = (ClassPathResource) resource; + return classPathResource.getPath(); + } + else { + throw ex; + } + } + } + + /** + * Sets the key store provider. + * + *

This property maps to the WSS4J {@code org.apache.ws.security.crypto.merlin.keystore.provider} property. + * + * @param provider the key store provider + */ + public void setKeyStoreProvider(String provider) { + this.configuration.setProperty("org.apache.ws.security.crypto.merlin.keystore.provider", provider); + } + + /** + * Sets the key store password. Defaults to {@code security}. + * + *

This property maps to the WSS4J {@code org.apache.ws.security.crypto.merlin.keystore.password} property. + * + * @param password the key store password + */ + public void setKeyStorePassword(String password) { + this.configuration.setProperty("org.apache.ws.security.crypto.merlin.keystore.password", password); + } + + /** + * Sets the key store type. Defaults to {@link java.security.KeyStore#getDefaultType()}. + * + *

This property maps to the WSS4J {@code org.apache.ws.security.crypto.merlin.keystore.type} property. + * + * @param type the key store type + */ + public void setKeyStoreType(String type) { + this.configuration.setProperty("org.apache.ws.security.crypto.merlin.keystore.type", type); + } + + /** + * Sets the trust store password. Defaults to {@code changeit}. + * + *

WSS4J crypto uses the standard J2SE trust store, i.e. {@code $JAVA_HOME/lib/security/cacerts}. + * + *

This property maps to the WSS4J {@code org.apache.ws.security.crypto.merlin.cacerts.password} property. + * + * @param password the trust store password + */ + public void setTrustStorePassword(String password) { + this.configuration.setProperty("org.apache.ws.security.crypto.merlin.cacerts.password", password); + } + + /** + * Sets the alias name of the default certificate which has been specified as a property. This should be the + * certificate that is used for signature and encryption. This alias corresponds to the certificate that should be + * used whenever KeyInfo is not present in a signed or an encrypted message. + * + *

This property maps to the WSS4J {@code org.apache.ws.security.crypto.merlin.keystore.alias} property. + * + * @param defaultX509Alias alias name of the default X509 certificate + */ + public void setDefaultX509Alias(String defaultX509Alias) { + this.configuration.setProperty("org.apache.ws.security.crypto.merlin.keystore.alias", defaultX509Alias); + } + + @Override + public void afterPropertiesSet() throws Exception { + if (!configuration.containsKey(CRYPTO_PROVIDER_PROPERTY)) { + configuration.setProperty(CRYPTO_PROVIDER_PROPERTY, Merlin.class.getName()); + } + this.crypto = CryptoFactory.getInstance(configuration); + } + + @Override + public Class getObjectType() { + return Crypto.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public Crypto getObject() throws Exception { + return crypto; + } + +} diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/support/package.html b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/support/package.html new file mode 100644 index 00000000..d2e98676 --- /dev/null +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/support/package.html @@ -0,0 +1,5 @@ + + +Contains support classes for working with WSS4J 2.0. + + \ No newline at end of file diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenTestCase.java index b6e7111f..ee77cb4c 100644 --- a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenTestCase.java +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j/Wss4jMessageInterceptorUsernameTokenTestCase.java @@ -146,6 +146,9 @@ public abstract class Wss4jMessageInterceptorUsernameTokenTestCase extends Wss4j interceptor.setSecurementPasswordType(WSConstants.PW_TEXT); } interceptor.setValidationCallbackHandler(callbackHandler); + + interceptor.setBspCompliant(false); + interceptor.afterPropertiesSet(); return interceptor; } diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jInterceptorTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jInterceptorTest.java new file mode 100644 index 00000000..11f0fac8 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jInterceptorTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jInterceptorTest extends Wss4jInterceptorTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorEncryptionTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorEncryptionTest.java new file mode 100644 index 00000000..b89e607b --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorEncryptionTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jMessageInterceptorEncryptionTest extends Wss4jMessageInterceptorEncryptionTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorHeaderTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorHeaderTest.java new file mode 100644 index 00000000..645fbdff --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorHeaderTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jMessageInterceptorHeaderTest extends Wss4jMessageInterceptorHeaderTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSignTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSignTest.java new file mode 100644 index 00000000..e590d374 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSignTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jMessageInterceptorSignTest extends Wss4jMessageInterceptorSignTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSoapActionTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSoapActionTest.java new file mode 100644 index 00000000..24c25012 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSoapActionTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jMessageInterceptorSoapActionTest extends Wss4jMessageInterceptorSoapActionTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java new file mode 100644 index 00000000..ff685ce9 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java @@ -0,0 +1,22 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest + extends Wss4jMessageInterceptorSpringSecurityCallbackHandlerTestCase { + +} \ No newline at end of file diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorTimestampTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorTimestampTest.java new file mode 100644 index 00000000..00c059f2 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorTimestampTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jMessageInterceptorTimestampTest extends Wss4jMessageInterceptorTimestampTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java new file mode 100644 index 00000000..24d7d556 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java @@ -0,0 +1,22 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jMessageInterceptorUsernameTokenSignatureTest + extends Wss4jMessageInterceptorUsernameTokenSignatureTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenTest.java new file mode 100644 index 00000000..28803872 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class AxiomWss4jMessageInterceptorUsernameTokenTest extends Wss4jMessageInterceptorUsernameTokenTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorX509Test.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorX509Test.java new file mode 100644 index 00000000..c429c4a9 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorX509Test.java @@ -0,0 +1,22 @@ +/* + * 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.wss4j2; + +/** @author tareq */ +public class AxiomWss4jMessageInterceptorX509Test extends Wss4jMessageInterceptorX509TestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jInterceptorTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jInterceptorTest.java new file mode 100644 index 00000000..9d23c193 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jInterceptorTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class SaajWss4jInterceptorTest extends Wss4jInterceptorTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorEncryptionTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorEncryptionTest.java new file mode 100644 index 00000000..a0dfe13b --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorEncryptionTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class SaajWss4jMessageInterceptorEncryptionTest extends Wss4jMessageInterceptorEncryptionTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorHeaderTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorHeaderTest.java new file mode 100644 index 00000000..07226b2f --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorHeaderTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class SaajWss4jMessageInterceptorHeaderTest extends Wss4jMessageInterceptorHeaderTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSignTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSignTest.java new file mode 100644 index 00000000..1ec930b0 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSignTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPHeader; +import javax.xml.soap.SOAPHeaderElement; +import javax.xml.soap.SOAPMessage; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.xml.transform.StringSource; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class SaajWss4jMessageInterceptorSignTest extends Wss4jMessageInterceptorSignTestCase { + + private static final String PAYLOAD = + "QQQ"; + + @Test + public void testSignAndValidate() throws Exception { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + interceptor.setSecurementActions("Signature"); + interceptor.setEnableSignatureConfirmation(false); + interceptor.setSecurementPassword("123456"); + interceptor.setSecurementUsername("rsaKey"); + SOAPMessage saajMessage = saajSoap11MessageFactory.createMessage(); + transformer.transform(new StringSource(PAYLOAD), new DOMResult(saajMessage.getSOAPBody())); + SoapMessage message = new SaajSoapMessage(saajMessage, saajSoap11MessageFactory); + MessageContext messageContext = new DefaultMessageContext(message, new SaajSoapMessageFactory(saajSoap11MessageFactory)); + + interceptor.secureMessage(message, messageContext); + + SOAPHeader header = ((SaajSoapMessage) message).getSaajMessage().getSOAPHeader(); + Iterator iterator = header.getChildElements(new QName( + "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security")); + assertTrue("No security header", iterator.hasNext()); + SOAPHeaderElement securityHeader = (SOAPHeaderElement) iterator.next(); + iterator = securityHeader.getChildElements(new QName("http://www.w3.org/2000/09/xmldsig#", "Signature")); + assertTrue("No signature header", iterator.hasNext()); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + message.writeTo(bos); + + MimeHeaders mimeHeaders = new MimeHeaders(); + mimeHeaders.addHeader("Content-Type", "text/xml"); + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + + SOAPMessage signed = saajSoap11MessageFactory.createMessage(mimeHeaders, bis); + message = new SaajSoapMessage(signed, saajSoap11MessageFactory); + messageContext = new DefaultMessageContext(message, new SaajSoapMessageFactory(saajSoap11MessageFactory)); + + interceptor.validateMessage(message, messageContext); + } + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSoapActionTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSoapActionTest.java new file mode 100644 index 00000000..11cf1fd8 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSoapActionTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class SaajWss4jMessageInterceptorSoapActionTest extends Wss4jMessageInterceptorSoapActionTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java new file mode 100644 index 00000000..7b774328 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java @@ -0,0 +1,22 @@ +/* + * 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.wss4j2; + +public class SaajWss4jMessageInterceptorSpringSecurityCallbackHandlerTest + extends Wss4jMessageInterceptorSpringSecurityCallbackHandlerTestCase { + +} \ No newline at end of file diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorTimestampTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorTimestampTest.java new file mode 100644 index 00000000..90bbcbd7 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorTimestampTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class SaajWss4jMessageInterceptorTimestampTest extends Wss4jMessageInterceptorTimestampTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorUsernameTokenSignatureTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorUsernameTokenSignatureTest.java new file mode 100644 index 00000000..8c55b27a --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorUsernameTokenSignatureTest.java @@ -0,0 +1,22 @@ +/* + * 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.wss4j2; + +public class SaajWss4jMessageInterceptorUsernameTokenSignatureTest + extends Wss4jMessageInterceptorUsernameTokenSignatureTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorUsernameTokenTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorUsernameTokenTest.java new file mode 100644 index 00000000..b380d069 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorUsernameTokenTest.java @@ -0,0 +1,21 @@ +/* + * 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.wss4j2; + +public class SaajWss4jMessageInterceptorUsernameTokenTest extends Wss4jMessageInterceptorUsernameTokenTestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorX509Test.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorX509Test.java new file mode 100644 index 00000000..de0563f0 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/SaajWss4jMessageInterceptorX509Test.java @@ -0,0 +1,22 @@ +/* + * 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.wss4j2; + +/** @author tareq */ +public class SaajWss4jMessageInterceptorX509Test extends Wss4jMessageInterceptorX509TestCase { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jInterceptorTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jInterceptorTestCase.java new file mode 100644 index 00000000..e446e78e --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jInterceptorTestCase.java @@ -0,0 +1,84 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +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; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public abstract class Wss4jInterceptorTestCase extends Wss4jTestCase { + + @Test + public void testHandleRequest() throws Exception { + SoapMessage request = loadSoap11Message("empty-soap.xml"); + final Object requestMessage = getMessage(request); + SoapMessage validatedRequest = loadSoap11Message("empty-soap.xml"); + final Object validatedRequestMessage = getMessage(validatedRequest); + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor() { + @Override + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecuritySecurementException { + fail("secure not expected"); + } + + @Override + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { + assertEquals("Invalid message", requestMessage, getMessage(soapMessage)); + setMessage(soapMessage, validatedRequestMessage); + } + }; + MessageContext context = new DefaultMessageContext(request, getSoap11MessageFactory()); + interceptor.handleRequest(context, null); + assertEquals("Invalid request", validatedRequestMessage, getMessage((SoapMessage) context.getRequest())); + } + + @Test + public void testHandleResponse() throws Exception { + SoapMessage securedResponse = loadSoap11Message("empty-soap.xml"); + final Object securedResponseMessage = getMessage(securedResponse); + + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor() { + + @Override + protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecuritySecurementException { + setMessage(soapMessage, securedResponseMessage); + } + + @Override + protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) + throws WsSecurityValidationException { + fail("validate not expected"); + } + + }; + SoapMessage request = loadSoap11Message("empty-soap.xml"); + MessageContext context = new DefaultMessageContext(request, getSoap11MessageFactory()); + context.getResponse(); + interceptor.handleResponse(context, null); + assertEquals("Invalid response", securedResponseMessage, getMessage((SoapMessage) context.getResponse())); + } + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorEncryptionTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorEncryptionTestCase.java new file mode 100644 index 00000000..56610ba7 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorEncryptionTestCase.java @@ -0,0 +1,86 @@ +/* + * Copyright 2005-2014 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.wss4j2; + +import java.util.Properties; + +import org.junit.Test; +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.wss4j2.callback.KeyStoreCallbackHandler; +import org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean; + +public abstract class Wss4jMessageInterceptorEncryptionTestCase extends Wss4jTestCase { + + protected Wss4jSecurityInterceptor interceptor; + + @Override + protected void onSetup() throws Exception { + interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidationActions("Encrypt"); + interceptor.setSecurementActions("Encrypt"); + + KeyStoreCallbackHandler callbackHandler = new KeyStoreCallbackHandler(); + callbackHandler.setPrivateKeyPassword("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(cryptoFactoryBean + .getObject()); + interceptor.setSecurementEncryptionCrypto(cryptoFactoryBean + .getObject()); + + interceptor.afterPropertiesSet(); + } + + @Test + public void testDecryptRequest() throws Exception { + SoapMessage message = loadSoap11Message("encrypted-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + 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)); + } + + @Test + public void testEncryptResponse() throws Exception { + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext messageContext = getSoap11MessageContext(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); + } +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorHeaderTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorHeaderTestCase.java new file mode 100644 index 00000000..3738aa6f --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorHeaderTestCase.java @@ -0,0 +1,160 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.util.Iterator; +import java.util.Properties; +import javax.xml.namespace.QName; + +import org.junit.Test; + +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.WsSecurityValidationException; +import org.springframework.ws.soap.security.wss4j2.callback.SimplePasswordValidationCallbackHandler; + +/** + * @author Arjen Poutsma + * @author Tareq Abedrabbo + * @author Greg Turnquist + */ +public abstract class Wss4jMessageInterceptorHeaderTestCase extends Wss4jTestCase { + + private Wss4jSecurityInterceptor interceptor; + private Wss4jSecurityInterceptor interceptorThatKeepsSecurityHeader; + + @Override + 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"); + SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler(); + callbackHandler.setUsers(users); + interceptor.setValidationCallbackHandler(callbackHandler); + interceptor.afterPropertiesSet(); + + interceptorThatKeepsSecurityHeader = new Wss4jSecurityInterceptor(); + interceptorThatKeepsSecurityHeader.setValidateRequest(true); + interceptorThatKeepsSecurityHeader.setSecureResponse(true); + interceptorThatKeepsSecurityHeader.setValidationActions("UsernameToken"); + interceptorThatKeepsSecurityHeader.setValidationCallbackHandler(callbackHandler); + interceptorThatKeepsSecurityHeader.setRemoveSecurityHeader(false); + interceptorThatKeepsSecurityHeader.afterPropertiesSet(); + } + + @Test + public void testValidateUsernameTokenPlainText() throws Exception { + SoapMessage message = loadSoap11Message("usernameTokenPlainTextWithHeaders-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.validateMessage(message, messageContext); + Object result = getMessage(message); + assertNotNull("No result returned", result); + + for (Iterator i = message.getEnvelope().getHeader().examineAllHeaderElements(); i.hasNext();) { + SoapHeaderElement element = 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)); + + } + + @Test + public void testValidateUsernameTokenPlainTextButKeepSecurityHeader() throws Exception { + SoapMessage message = loadSoap11Message("usernameTokenPlainTextWithHeaders-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptorThatKeepsSecurityHeader.validateMessage(message, messageContext); + Object result = getMessage(message); + assertNotNull("No result returned", result); + + boolean foundSecurityHeader = false; + for (Iterator i = message.getEnvelope().getHeader().examineAllHeaderElements(); i.hasNext();) { + SoapHeaderElement element = 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")) { + foundSecurityHeader = true; + } + + } + assertTrue(foundSecurityHeader); + + 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)); + + } + + @Test(expected=WsSecurityValidationException.class) + public void testEmptySecurityHeader() throws Exception { + SoapMessage message = loadSoap11Message("emptySecurityHeader-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.validateMessage(message, messageContext); + } + + @Test + public void testPreserveCustomHeaders() throws Exception { + interceptor.setSecurementActions("UsernameToken"); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + SoapMessage message = loadSoap11Message("customHeader-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + message.writeTo(os); + String document = os.toString("UTF-8"); + assertXpathEvaluatesTo("Header 1 does not exist", "test1", "/SOAP-ENV:Envelope/SOAP-ENV:Header/test:header1", + document); + assertXpathNotExists("Header 2 exist", "/SOAP-ENV:Envelope/SOAP-ENV:Header/test:header2", document); + + interceptor.secureMessage(message, messageContext); + + SoapHeaderElement element = message.getSoapHeader().addHeaderElement(new QName("http://test", "header2")); + element.setText("test2"); + + os = new ByteArrayOutputStream(); + message.writeTo(os); + document = os.toString("UTF-8"); + assertXpathEvaluatesTo("Header 1 does not exist", "test1", "/SOAP-ENV:Envelope/SOAP-ENV:Header/test:header1", + document); + assertXpathEvaluatesTo("Header 2 does not exist", "test2", "/SOAP-ENV:Envelope/SOAP-ENV:Header/test:header2", + document); + + os = new ByteArrayOutputStream(); + message.writeTo(os); + document = os.toString("UTF-8"); + assertXpathEvaluatesTo("Header 1 does not exist", "test1", "/SOAP-ENV:Envelope/SOAP-ENV:Header/test:header1", + document); + assertXpathEvaluatesTo("Header 2 does not exist", "test2", "/SOAP-ENV:Envelope/SOAP-ENV:Header/test:header2", + document); + } +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java new file mode 100644 index 00000000..1f20520b --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java @@ -0,0 +1,123 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +import java.util.Properties; + +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.wss4j2.support.CryptoFactoryBean; + +import org.junit.Test; +import org.w3c.dom.Document; + +import static org.junit.Assert.assertNotNull; + +public abstract class Wss4jMessageInterceptorSignTestCase extends Wss4jTestCase { + + protected Wss4jSecurityInterceptor interceptor; + + @Override + protected void onSetup() throws Exception { + interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidationActions("Signature"); + + 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(cryptoFactoryBean + .getObject()); + interceptor.setSecurementSignatureCrypto(cryptoFactoryBean + .getObject()); + interceptor.afterPropertiesSet(); + + } + + @Test + public void testValidateCertificate() throws Exception { + SoapMessage message = loadSoap11Message("signed-soap.xml"); + + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + 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)); + } + + @Test + public void testValidateCertificateWithSignatureConfirmation() throws Exception { + SoapMessage message = loadSoap11Message("signed-soap.xml"); + MessageContext messageContext = getSoap11MessageContext(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); + assertXpathExists("Absent SignatureConfirmation element", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse11:SignatureConfirmation", document); + } + + @Test + public void testSignResponse() throws Exception { + interceptor.setSecurementActions("Signature"); + interceptor.setEnableSignatureConfirmation(false); + interceptor.setSecurementPassword("123456"); + interceptor.setSecurementUsername("rsaKey"); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext messageContext = getSoap11MessageContext(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); + + + } + + @Test + public void testSignResponseWithSignatureUser() throws Exception { + interceptor.setSecurementActions("Signature"); + interceptor.setEnableSignatureConfirmation(false); + interceptor.setSecurementPassword("123456"); + interceptor.setSecurementSignatureUser("rsaKey"); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext messageContext = getSoap11MessageContext(message); + + 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/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSoapActionTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSoapActionTestCase.java new file mode 100644 index 00000000..1cfda387 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSoapActionTestCase.java @@ -0,0 +1,107 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +import java.util.Properties; + +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.security.wss4j2.callback.SimplePasswordValidationCallbackHandler; +import org.apache.wss4j.dom.WSConstants; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public abstract class Wss4jMessageInterceptorSoapActionTestCase extends Wss4jTestCase { + + private static final String SOAP_ACTION = "\"http://test\""; + + private Properties users; + + private Wss4jSecurityInterceptor interceptor; + + @Override + protected void onSetup() throws Exception { + users = new Properties(); + users.setProperty("Bert", "Ernie"); + interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidationActions("UsernameToken"); + interceptor.setSecurementActions("UsernameToken"); + interceptor.setSecurementPasswordType(WSConstants.PW_TEXT); + SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler(); + callbackHandler.setUsers(users); + interceptor.setValidationCallbackHandler(callbackHandler); + + interceptor.afterPropertiesSet(); + } + + @Test + public void testPreserveSoapActionOnValidation() throws Exception { + SoapMessage message = loadSoap11Message("usernameTokenPlainText-soap.xml"); + message.setSoapAction(SOAP_ACTION); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.validateMessage(message, messageContext); + + assertNotNull("Soap Action must not be null", message.getSoapAction()); + assertEquals("Soap Action is different from expected", SOAP_ACTION, message.getSoapAction()); + } + + @Test + public void testPreserveSoap12ActionOnValidation() throws Exception { + SoapMessage message = loadSoap12Message("usernameTokenPlainText-soap12.xml"); + message.setSoapAction(SOAP_ACTION); + WebServiceMessageFactory messageFactory = getSoap12MessageFactory(); + MessageContext messageContext = new DefaultMessageContext(message, messageFactory); + interceptor.validateMessage(message, messageContext); + + assertNotNull("Soap Action must not be null", message.getSoapAction()); + assertEquals("Soap Action is different from expected", SOAP_ACTION, message.getSoapAction()); + } + + @Test + public void testPreserveSoapActionOnSecurement() throws Exception { + SoapMessage message = loadSoap11Message("empty-soap.xml"); + message.setSoapAction(SOAP_ACTION); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + MessageContext messageContext = getSoap11MessageContext(message); + interceptor.secureMessage(message, messageContext); + + assertNotNull("Soap Action must not be null", message.getSoapAction()); + assertEquals("Soap Action is different from expected", SOAP_ACTION, message.getSoapAction()); + + } + + @Test + public void testPreserveSoap12ActionOnSecurement() throws Exception { + SoapMessage message = loadSoap12Message("empty-soap12.xml"); + message.setSoapAction(SOAP_ACTION); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + MessageContext messageContext = getSoap12MessageContext(message); + interceptor.secureMessage(message, messageContext); + + assertNotNull("Soap Action must not be null", message.getSoapAction()); + assertEquals("Soap Action is different from expected", SOAP_ACTION, message.getSoapAction()); + + } + + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSpringSecurityCallbackHandlerTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSpringSecurityCallbackHandlerTestCase.java new file mode 100644 index 00000000..a677763a --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSpringSecurityCallbackHandlerTestCase.java @@ -0,0 +1,126 @@ +/* + * Copyright 2005-2012 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.wss4j2; + +import java.util.Properties; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +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.wss4j2.callback.SpringSecurityPasswordValidationCallbackHandler; +import org.apache.wss4j.dom.WSConstants; +import org.junit.After; +import org.junit.Test; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public abstract class Wss4jMessageInterceptorSpringSecurityCallbackHandlerTestCase extends Wss4jTestCase { + + private Properties users = new Properties(); + + private AuthenticationManager authenticationManager; + + @Override + protected void onSetup() throws Exception { + authenticationManager = createMock(AuthenticationManager.class); + users.setProperty("Bert", "Ernie,ROLE_TEST"); + } + + @After + public void tearDown() throws Exception { + verify(authenticationManager); + SecurityContextHolder.clearContext(); + } + + @Test + public void testValidateUsernameTokenPlainText() throws Exception { + EndpointInterceptor interceptor = prepareInterceptor("UsernameToken", true, false); + SoapMessage message = loadSoap11Message("usernameTokenPlainText-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.handleRequest(messageContext, null); + assertValidateUsernameToken(message); + + // test clean up + messageContext.getResponse(); + interceptor.handleResponse(messageContext, null); + interceptor.afterCompletion(messageContext, null, null); + assertNull("Authentication created", SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + public void testValidateUsernameTokenDigest() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("UsernameToken"); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST); + + + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.handleRequest(messageContext); + + interceptor = prepareInterceptor("UsernameToken", true, true); + interceptor.handleRequest(messageContext, null); + assertValidateUsernameToken(message); + + // test clean up + messageContext.getResponse(); + interceptor.handleResponse(messageContext, null); + interceptor.afterCompletion(messageContext, null, null); + assertNull("Authentication created", SecurityContextHolder.getContext().getAuthentication()); + } + + 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)); + assertNotNull("No Authentication created", SecurityContextHolder.getContext().getAuthentication()); + } + + protected Wss4jSecurityInterceptor prepareInterceptor(String actions, boolean validating, boolean digest) + throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + if (validating) { + interceptor.setValidationActions(actions); + } + else { + interceptor.setSecurementActions(actions); + } + SpringSecurityPasswordValidationCallbackHandler callbackHandler = + new SpringSecurityPasswordValidationCallbackHandler(); + InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(users); + callbackHandler.setUserDetailsService(userDetailsManager); + if (digest) { + interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST); + } + else { + interceptor.setSecurementPasswordType(WSConstants.PW_TEXT); + } + interceptor.setValidationCallbackHandler(callbackHandler); + interceptor.afterPropertiesSet(); + replay(authenticationManager); + return interceptor; + } +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorTimestampTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorTimestampTestCase.java new file mode 100644 index 00000000..d396f0bf --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorTimestampTestCase.java @@ -0,0 +1,103 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.WsSecurityValidationException; + +import org.junit.Test; +import org.w3c.dom.Document; + +import static org.junit.Assert.assertEquals; + +public abstract class Wss4jMessageInterceptorTimestampTestCase extends Wss4jTestCase { + + @Test + public void testAddTimestamp() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("Timestamp"); + interceptor.afterPropertiesSet(); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext context = getSoap11MessageContext(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); + } + + @Test + public void testValidateTimestamp() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidationActions("Timestamp"); + interceptor.afterPropertiesSet(); + SoapMessage message = getMessageWithTimestamp(); + + MessageContext context = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.validateMessage(message, context); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", + getDocument(message)); + } + + @Test(expected = WsSecurityValidationException.class) + public void testValidateTimestampWithExpiredTtl() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setValidationActions("Timestamp"); + interceptor.afterPropertiesSet(); + SoapMessage message = loadSoap11Message("expiredTimestamp-soap.xml"); + MessageContext context = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.validateMessage(message, context); + } + + + @Test + public void testSecureTimestampWithCustomTtl() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("Timestamp"); + interceptor.setTimestampStrict(true); + int ttlInSeconds = 1; + interceptor.setSecurementTimeToLive(ttlInSeconds); + interceptor.afterPropertiesSet(); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext context = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.secureMessage(message, context); + + String created = xpathTemplate.evaluateAsString("/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsu:Timestamp/wsu:Created/text()", + message.getEnvelope().getSource()); + String expires = xpathTemplate.evaluateAsString("/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsu:Timestamp/wsu:Expires/text()", + message.getEnvelope().getSource()); + + DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SS'Z'"); + + long actualTtl = format.parse(expires).getTime() - format.parse(created).getTime(); + assertEquals("invalid ttl", 1000 * ttlInSeconds, actualTtl); + } + + private SoapMessage getMessageWithTimestamp() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("Timestamp"); + interceptor.afterPropertiesSet(); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext context = getSoap11MessageContext(message); + interceptor.secureMessage(message, context); + return message; + } +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorUsernameTokenSignatureTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorUsernameTokenSignatureTestCase.java new file mode 100644 index 00000000..e99f7a01 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorUsernameTokenSignatureTestCase.java @@ -0,0 +1,45 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; + +import org.junit.Test; +import org.w3c.dom.Document; + +public abstract class Wss4jMessageInterceptorUsernameTokenSignatureTestCase extends Wss4jTestCase { + + @Test + public void testAddUsernameTokenSignature() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("UsernameToken"); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + interceptor.afterPropertiesSet(); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext context = getSoap11MessageContext(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/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorUsernameTokenTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorUsernameTokenTestCase.java new file mode 100644 index 00000000..be7cda7a --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorUsernameTokenTestCase.java @@ -0,0 +1,154 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +import static org.junit.Assert.assertNotNull; + +import java.util.Properties; + +import org.apache.wss4j.dom.WSConstants; +import org.junit.Test; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.wss4j2.callback.SimplePasswordValidationCallbackHandler; +import org.w3c.dom.Document; + +public abstract class Wss4jMessageInterceptorUsernameTokenTestCase extends Wss4jTestCase { + + private Properties users = new Properties(); + + @Override + protected void onSetup() throws Exception { + users.setProperty("Bert", "Ernie"); + } + + @Test + public void testValidateUsernameTokenPlainText() throws Exception { + Wss4jSecurityInterceptor interceptor = prepareInterceptor("UsernameToken", true, false); + SoapMessage message = loadSoap11Message("usernameTokenPlainText-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.validateMessage(message, messageContext); + assertValidateUsernameToken(message); + } + + @Test + public void testValidateUsernameTokenDigest() throws Exception { + Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("UsernameToken"); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST); + + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.handleRequest(messageContext); + + interceptor = prepareInterceptor("UsernameToken", true, true); + interceptor.validateMessage(message, messageContext); + assertValidateUsernameToken(message); + } + + @Test + public void testValidateUsernameTokenWithQualifiedType() throws Exception { + Wss4jSecurityInterceptor interceptor = prepareInterceptor("UsernameToken", true, false); + SoapMessage message = loadSoap11Message("usernameTokenPlainTextQualifiedType-soap.xml"); + MessageContext messageContext = new DefaultMessageContext(message, getSoap11MessageFactory()); + interceptor.validateMessage(message, messageContext); + assertValidateUsernameToken(message); + } + + @Test + public void testAddUsernameTokenPlainText() throws Exception { + Wss4jSecurityInterceptor interceptor = prepareInterceptor("UsernameToken", false, false); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + + MessageContext messageContext = getSoap11MessageContext(message); + + interceptor.secureMessage(message, messageContext); + assertAddUsernameTokenPlainText(message); + } + + @Test + public void testAddUsernameTokenDigest() throws Exception { + Wss4jSecurityInterceptor interceptor = prepareInterceptor("UsernameToken", false, true); + interceptor.setSecurementUsername("Bert"); + interceptor.setSecurementPassword("Ernie"); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + + MessageContext messageContext = getSoap11MessageContext(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); + } + SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler(); + callbackHandler.setUsers(users); + if (digest) { + interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST); + } + else { + interceptor.setSecurementPasswordType(WSConstants.PW_TEXT); + } + interceptor.setValidationCallbackHandler(callbackHandler); + + interceptor.setBspCompliant(false); + + interceptor.afterPropertiesSet(); + return interceptor; + } +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorX509TestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorX509TestCase.java new file mode 100644 index 00000000..3ae55683 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorX509TestCase.java @@ -0,0 +1,72 @@ +/* + * Copyright 2005-2014 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.wss4j2; + +import org.apache.wss4j.common.crypto.Merlin; +import org.junit.Test; +import org.w3c.dom.Document; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean; + +public abstract class Wss4jMessageInterceptorX509TestCase extends Wss4jTestCase { + + protected Wss4jSecurityInterceptor interceptor; + + @Override + protected void onSetup() throws Exception { + interceptor = new Wss4jSecurityInterceptor(); + interceptor.setSecurementActions("Signature"); + interceptor.setValidationActions("Signature"); + CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean(); + cryptoFactoryBean.setCryptoProvider(Merlin.class); + cryptoFactoryBean.setKeyStoreType("jceks"); + cryptoFactoryBean.setKeyStorePassword("123456"); + cryptoFactoryBean.setKeyStoreLocation(new ClassPathResource("private.jks")); + + cryptoFactoryBean.afterPropertiesSet(); + interceptor.setSecurementSignatureCrypto(cryptoFactoryBean + .getObject()); + interceptor.setValidationSignatureCrypto(cryptoFactoryBean + .getObject()); + interceptor.afterPropertiesSet(); + + } + + @Test + public void testAddCertificate() throws Exception { + + interceptor.setSecurementPassword("123456"); + interceptor.setSecurementUsername("rsaKey"); + SoapMessage message = loadSoap11Message("empty-soap.xml"); + MessageContext messageContext = getSoap11MessageContext(message); + + interceptor.setSecurementSignatureKeyIdentifier("DirectReference"); + + interceptor.secureMessage(message, messageContext); + Document document = getDocument(message); + + assertXpathExists("Absent BinarySecurityToken element", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:BinarySecurityToken", document); + + // lets verify the signature that we've just generated + interceptor.validateMessage(message, messageContext); + } + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jTestCase.java new file mode 100644 index 00000000..2bdeeee2 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jTestCase.java @@ -0,0 +1,281 @@ +/* + * Copyright 2005-2010 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.wss4j2; + +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.SOAPConstants; +import javax.xml.soap.SOAPMessage; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +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.SoapMessageFactory; +import org.springframework.ws.soap.SoapVersion; +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.transform.StringSource; +import org.springframework.xml.xpath.Jaxp13XPathTemplate; + +import org.apache.axiom.soap.SOAP12Constants; +import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder; +import org.junit.Assert; +import org.junit.Before; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import static org.junit.Assert.assertTrue; + +public abstract class Wss4jTestCase { + + protected MessageFactory saajSoap11MessageFactory; + + protected MessageFactory saajSoap12MessageFactory; + + protected final boolean axiomTest = this.getClass().getSimpleName().startsWith("Axiom"); + + protected final boolean saajTest = this.getClass().getSimpleName().startsWith("Saaj"); + + protected Jaxp13XPathTemplate xpathTemplate = new Jaxp13XPathTemplate(); + + @Before + public final void setUp() throws Exception { + if (!axiomTest && !saajTest) { + throw new IllegalArgumentException("test class name must start with either Axiom or Saaj"); + } + saajSoap11MessageFactory = MessageFactory.newInstance(); + saajSoap12MessageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + Map 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/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"); + namespaces.put("test", "http://test"); + xpathTemplate.setNamespaces(namespaces); + onSetup(); + } + + protected void assertXpathEvaluatesTo(String message, + String expectedValue, + String xpathExpression, + Document document) { + String actualValue = xpathTemplate.evaluateAsString(xpathExpression, new DOMSource(document)); + Assert.assertEquals(message, expectedValue, actualValue); + } + + protected void assertXpathEvaluatesTo(String message, + String expectedValue, + String xpathExpression, + String document) { + String actualValue = xpathTemplate.evaluateAsString(xpathExpression, new StringSource(document)); + Assert.assertEquals(message, expectedValue, actualValue); + } + + protected void assertXpathExists(String message, String xpathExpression, Document document) { + Node node = xpathTemplate.evaluateAsNode(xpathExpression, new DOMSource(document)); + Assert.assertNotNull(message, node); + } + + protected void assertXpathNotExists(String message, String xpathExpression, Document document) { + Node node = xpathTemplate.evaluateAsNode(xpathExpression, new DOMSource(document)); + Assert.assertNull(message, node); + } + + protected void assertXpathNotExists(String message, String xpathExpression, String document) { + Node node = xpathTemplate.evaluateAsNode(xpathExpression, new StringSource(document)); + Assert.assertNull(message, node); + } + + protected SaajSoapMessage loadSaaj11Message(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(saajSoap11MessageFactory.createMessage(mimeHeaders, is), saajSoap11MessageFactory); + } + finally { + is.close(); + } + } + + protected SaajSoapMessage loadSaaj12Message(String fileName) throws Exception { + MimeHeaders mimeHeaders = new MimeHeaders(); + mimeHeaders.addHeader("Content-Type", "application/soap+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(saajSoap12MessageFactory.createMessage(mimeHeaders, is), saajSoap12MessageFactory); + } + finally { + is.close(); + } + } + + protected AxiomSoapMessage loadAxiom11Message(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, true); + } + finally { + is.close(); + } + } + + @SuppressWarnings("Since15") + protected AxiomSoapMessage loadAxiom12Message(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, SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI); + org.apache.axiom.soap.SOAPMessage soapMessage = builder.getSoapMessage(); + return new AxiomSoapMessage(soapMessage, "", true, 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 loadSoap11Message(String fileName) throws Exception { + if (axiomTest) { + return loadAxiom11Message(fileName); + } + if (saajTest) { + return loadSaaj11Message(fileName); + } + throw new IllegalArgumentException(); + } + + protected SoapMessage loadSoap12Message(String fileName) throws Exception { + if (axiomTest) { + return loadAxiom12Message(fileName); + } + if (saajTest) { + return loadSaaj12Message(fileName); + } + throw new IllegalArgumentException(); + } + + protected SoapMessageFactory getSoap11MessageFactory() throws Exception { + if (axiomTest) { + return new AxiomSoapMessageFactory(); + } + if (saajTest) { + return new SaajSoapMessageFactory(saajSoap11MessageFactory); + } + throw new IllegalArgumentException(); + } + + protected SoapMessageFactory getSoap12MessageFactory() throws Exception { + SoapMessageFactory messageFactory; + if (axiomTest) { + messageFactory = new AxiomSoapMessageFactory(); + } else if (saajTest) { + messageFactory = new SaajSoapMessageFactory(saajSoap12MessageFactory); + } else + throw new IllegalArgumentException(); + messageFactory.setSoapVersion(SoapVersion.SOAP_12); + return messageFactory; + } + + 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 getSoap11MessageContext(final SoapMessage response) throws Exception { + return new DefaultMessageContext(response, getSoap11MessageFactory()) { + @Override + public WebServiceMessage getResponse() { + return response; + } + }; + } + + protected MessageContext getSoap12MessageContext(final SoapMessage response) throws Exception { + return new DefaultMessageContext(response, getSoap12MessageFactory()) { + @Override + public WebServiceMessage getResponse() { + return response; + } + }; + } + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/callback/KeyStoreCallbackHandlerTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/callback/KeyStoreCallbackHandlerTest.java new file mode 100644 index 00000000..40dfe521 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/callback/KeyStoreCallbackHandlerTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2005-2012 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.wss4j2.callback; + +import java.security.KeyStore; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.ws.soap.security.support.KeyStoreFactoryBean; +import org.apache.wss4j.common.ext.WSPasswordCallback; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class KeyStoreCallbackHandlerTest { + + private KeyStoreCallbackHandler callbackHandler; + + private WSPasswordCallback callback; + + @Before + public void setUp() throws Exception { + callbackHandler = new KeyStoreCallbackHandler(); + callback = new WSPasswordCallback("secretkey", WSPasswordCallback.SECRET_KEY); + + KeyStoreFactoryBean factory = new KeyStoreFactoryBean(); + factory.setLocation(new ClassPathResource("private.jks")); + factory.setPassword("123456"); + factory.setType("JCEKS"); + factory.afterPropertiesSet(); + KeyStore keyStore = factory.getObject(); + callbackHandler.setKeyStore(keyStore); + callbackHandler.setSymmetricKeyPassword("123456"); + } + + @Test + public void testHandleKeyName() throws Exception { + callbackHandler.handleInternal(callback); + Assert.assertNotNull("symmetric key must not be null", callback.getKey()); + } + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/callback/SpringSecurityPasswordValidationCallbackHandlerTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/callback/SpringSecurityPasswordValidationCallbackHandlerTest.java new file mode 100644 index 00000000..e3b076d0 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/callback/SpringSecurityPasswordValidationCallbackHandlerTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2005-2012 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.wss4j2.callback; + +import java.util.Collection; +import java.util.Collections; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.apache.wss4j.common.principal.WSUsernameTokenPrincipalImpl; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.easymock.EasyMock.*; + +/** @author tareq */ +public class SpringSecurityPasswordValidationCallbackHandlerTest { + + private SpringSecurityPasswordValidationCallbackHandler callbackHandler; + + private SimpleGrantedAuthority grantedAuthority; + + private UsernameTokenPrincipalCallback callback; + + private UserDetails user; + + @Before + public void setUp() throws Exception { + callbackHandler = new SpringSecurityPasswordValidationCallbackHandler(); + + grantedAuthority = new SimpleGrantedAuthority("ROLE_1"); + user = new User("Ernie", "Bert", true, true, true, true, Collections.singleton(grantedAuthority)); + + WSUsernameTokenPrincipalImpl principal = new WSUsernameTokenPrincipalImpl("Ernie", true); + callback = new UsernameTokenPrincipalCallback(principal); + } + + @Test + public void testHandleUsernameTokenPrincipal() throws Exception { + UserDetailsService userDetailsService = createMock(UserDetailsService.class); + callbackHandler.setUserDetailsService(userDetailsService); + + expect(userDetailsService.loadUserByUsername("Ernie")).andReturn(user).anyTimes(); + + replay(userDetailsService); + + callbackHandler.handleUsernameTokenPrincipal(callback); + SecurityContext context = SecurityContextHolder.getContext(); + Assert.assertNotNull("SecurityContext must not be null", context); + Authentication authentication = context.getAuthentication(); + Assert.assertNotNull("Authentication must not be null", authentication); + Collection authorities = authentication.getAuthorities(); + Assert.assertTrue("GrantedAuthority[] must not be null or empty", + (authorities != null && authorities.size() > 0)); + Assert.assertEquals("Unexpected authority", grantedAuthority, authorities.iterator().next()); + + verify(userDetailsService); + } +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/support/CryptoFactoryBeanTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/support/CryptoFactoryBeanTest.java new file mode 100644 index 00000000..1c3f1d4d --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/support/CryptoFactoryBeanTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2005-2010 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.wss4j2.support; + +import java.util.Properties; + +import org.apache.wss4j.common.crypto.Merlin; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +public class CryptoFactoryBeanTest { + + private CryptoFactoryBean factoryBean; + + @Before + public void setUp() throws Exception { + factoryBean = new CryptoFactoryBean(); + } + + @Test + public void testSetConfiguration() throws Exception { + Properties configuration = new Properties(); + configuration.setProperty("org.apache.ws.security.crypto.provider", + "org.apache.ws.security.components.crypto.Merlin"); + configuration.setProperty("org.apache.ws.security.crypto.merlin.keystore.type", "jceks"); + configuration.setProperty("org.apache.ws.security.crypto.merlin.keystore.password", "123456"); + configuration.setProperty("org.apache.ws.security.crypto.merlin.file", "private.jks"); + + factoryBean.setConfiguration(configuration); + factoryBean.afterPropertiesSet(); + + Object result = factoryBean.getObject(); + Assert.assertNotNull("No result", result); + Assert.assertTrue("Not a Merlin instance", result instanceof Merlin); + } + + @Test + public void testProperties() throws Exception { + factoryBean.setKeyStoreType("jceks"); + factoryBean.setKeyStorePassword("123456"); + factoryBean.setKeyStoreLocation(new ClassPathResource("private.jks")); + factoryBean.afterPropertiesSet(); + Object result = factoryBean.getObject(); + Assert.assertNotNull("No result", result); + Assert.assertTrue("Not a Merlin instance", result instanceof Merlin); + } +} \ No newline at end of file diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/customHeader-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/customHeader-soap.xml new file mode 100644 index 00000000..3c56bb6f --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/customHeader-soap.xml @@ -0,0 +1,9 @@ + + + + test1 + + + QQQ + + diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/echo-response.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/echo-response.xml new file mode 100644 index 00000000..c529fdf2 --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/echo-response.xml @@ -0,0 +1,6 @@ + + + + Hello + + \ No newline at end of file diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/empty-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/empty-soap.xml new file mode 100644 index 00000000..18e3966d --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/empty-soap.xml @@ -0,0 +1,5 @@ + + + QQQ + + diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/empty-soap12.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/empty-soap12.xml new file mode 100644 index 00000000..3805b39c --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/empty-soap12.xml @@ -0,0 +1,5 @@ + + + QQQ + + diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/emptySecurityHeader-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/emptySecurityHeader-soap.xml new file mode 100644 index 00000000..08aa1c65 --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/emptySecurityHeader-soap.xml @@ -0,0 +1,10 @@ + + + + + + + QQQ + + diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/encrypted-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/encrypted-soap.xml new file mode 100644 index 00000000..c4f2315c --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/encrypted-soap.xml @@ -0,0 +1,20 @@ + + + + + + +CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown + +1204234455 + + + +gqgcmkwA9dsfFQNyt8V+ztLMCMy7DZ/DbQ3Yrt3XSh8E9qVBwrZmWV5LUw2qrxpcTCYQK3NK4kuc9IVedAF3PzKiYKOWG/fN01Cpk63vVUox6gqp9N4DZRSkfvxVbuxzUdyrsvn+WUYPKjpHFbQdvSzduydPgrleLBFodl021Lk= + + +HrhKCP62Qn2yxsu4CKsTyNoMxJuXQji6uubgMlYD1j/+kLEfUZwlzWYvu1hHW2nnjZI2LDoMaktE +SUlbafeejM9JJ7LNaTSs0dzzpjhmbm0a8E5i0B+Fth62zHB8L4fZZVYVDqLUn37CQTGASTVJDt94 +NJRF/Fk0JPyW32iCRpPvrwaJzw/45mkYMaqPsvvcj7VKr4EoH96d2ilRENZXuIE6MiR+nBmyfdgl +21Mv8q04uwCTWBykDqrk95QG07sXInAsp98rTR6kmU+8ntftEVpTGP9TEMQ+7SaXHXH2U3IqITo0 +e5dCizixVlotEn+X \ No newline at end of file diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/expiredTimestamp-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/expiredTimestamp-soap.xml new file mode 100644 index 00000000..75239e4c --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/expiredTimestamp-soap.xml @@ -0,0 +1,14 @@ + + + + + + 2009-12-25T15:43:22.687Z + 2009-12-25T15:48:22.687Z + + + + + QQQ + + \ No newline at end of file diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/signed-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/signed-soap.xml new file mode 100644 index 00000000..c2f01286 --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/signed-soap.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + +70oDCsbAmt7qauS7FmUvB7oVDfM= + + + +lUrzlFJIz4SRTf+qP9zPZr0s9tN+Suu+XJ2iwVwSMoJIfw+1YQSnWrQZD7EctacGN8iAHqP5g/LK +mXtOw0Ar03uwjoeUBqmchcIjWZjyYYmvtTDF9VA70z07C4zp7FYAP7GLKVCb4hmFn81mHLhWYlo7 +2bQEpkIOjaC5IBoFiLI= + + + + +CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown + +1204234455 + + + +Hello diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenDigest-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenDigest-soap.xml new file mode 100644 index 00000000..98d82b7a --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenDigest-soap.xml @@ -0,0 +1,6 @@ + + +BertkwNstEaiFOrI7B31j7GuETYvdgk=9mdsYDCrjjYRur0rxzYt2oD72006-06-01T23:48:42Z + QQQ + + diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainText-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainText-soap.xml new file mode 100644 index 00000000..7c228e00 --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainText-soap.xml @@ -0,0 +1,19 @@ + + + + + Bert + Ernie + + + + + QQQ + + \ No newline at end of file diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainText-soap12.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainText-soap12.xml new file mode 100644 index 00000000..b0d687c2 --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainText-soap12.xml @@ -0,0 +1,19 @@ + + + + + Bert + Ernie + + + + + QQQ + + \ No newline at end of file diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainTextQualifiedType-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainTextQualifiedType-soap.xml new file mode 100644 index 00000000..8d96909c --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainTextQualifiedType-soap.xml @@ -0,0 +1,17 @@ + + + + + Bert + Ernie + + + + + QQQ + + \ No newline at end of file diff --git a/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainTextWithHeaders-soap.xml b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainTextWithHeaders-soap.xml new file mode 100644 index 00000000..ae88469b --- /dev/null +++ b/spring-ws-security/src/test/resources/org/springframework/ws/soap/security/wss4j2/usernameTokenPlainTextWithHeaders-soap.xml @@ -0,0 +1,21 @@ + + + 1 + + + Bert + Ernie + + + 2 + + + QQQ + + \ No newline at end of file