WS Security Implementation Using Apache CXF and WSS4J


Create A Web Service And Client using Apache CXF


There are many web service frameworks available today and most of them support WS Security. In this tutorial we are going to use Apache CXF framework with Spring Framework. As we are going to focus mostly on security I am not going to explain in detail how a CXF-Spring web service and client are written. Web Service itself is a huge topic! But we do need to write a simple service and a client to explain the WS Security stuff. I would recommend you to learn basics of Spring and Apache CXF framework and understand how they integrate with each other and understand the configuration files. For this tutorial purpose I have written a simple hello world service and a client. Please go through the page Web Service and Client Using Apache CXF and get your setup ready to dive in further!

A Quick Tour of WSS4J


CXF implements WS-Security by integrating WSS4J. Apache Axis framework also uses WSS4J. The Apache WSS4J project provides a Java implementation of the primary security standards for Web Services, namely the OASIS Web Services Security (WS-Security) specifications. Through a number of standards such as XML-Encryption, XML-Signature and headers defined in the WS-Security standard, it allows you to:
  • Pass authentication tokens between services
  • Sign messages
  • Encrypt messages or parts of messages 
  • Timestamp messages
In this page we are not going to show Username Token, i.e. pass authentication token in SOAP message. But we shall see the rest i.e. signing, encrypting and time-stamping messages. Username token will be covered at WS Security With UsernameToken page. Normally username token is not used when client signs a message using a digital certificate. Digital certificate itself is the identity of the client. 

There are two ways CXF allows us to configure WS Security using WSS4J. 
  • Using standard WSS4J interceptors
  • Using WS SecurityPolicy Framework
In part 6 of this tutorial we shall talk about WS SecurityPolicy and show how in CXF that is implemented. Here we are going to work with standard WSS4J interceptors in CXF. To configure WSS4J with CXF we basically need to follow four steps assuming you have got all the dependent libraries in your projects in both client and server side.
  1. Get your client and server certificates, keystore and truststore ready. Make them available in classpath.
  2. Write a property file to point to keystore and truststore properties (location, password, alias etc) to feed them to WSS4J. These properties are called crypto properties.
  3. Write a password callback handler class to tell WSS4J about the private key password in the key store.
  4. Write inbound and outbound interceptor beans. This beans will have a bunch of key value pairs in a map to define the characteristics of the security mechanism.
The crypto properties that we need to provide in a property file, in step 2, and key/value pairs that we need to define in inbound and outbound interceptor beans to characterize the security mechanism, in step 4, are defined at WSS4J configuration page. It is worth looking at this page. As you have already understood the web security specification now you would be able to relate key/value pairs, also called WSHandler configuration tags, mentioned in the page.


Preparing Keystores and Certificates


As you may have guessed that after creating a basic service and client we need digital certificates to start implementing WS Security. This section prepares required server and client side digital certificates. 

Server Certificates and KeyStore

We have already learnt how to create certificates using Java Keytool and get it signed by a CA using OenSSL. We are going to repeat that here again. Under C drive create a folder C:\>WSST\KeyStores. I have put the commands at server key store page in order to avoid this page getting unnecessary lengthy. Follow the steps as mentioned in the page and you should be done with server ceritficates and keystore. Now copy the server key store in HelloWorldService project's config directory.

C:\WSST\KeyStores>copy ServerKeyStore.jks ..\HelloWorldService\config\ServerKeyStore.jks
        1 file(s) copied.

Client Certificates and KeyStore

Like server key store we need to prepare the client key store as well. Check client key store page for the commands. Copy the ClientKeyStore.jks in HelloWorldClient's config directory.

C:\WSST\KeyStores>copy ClientKeyStore.jks ..\HelloWorldClient\config\ClientKeyStore.jks
        1 file(s) copied.

Create Crypto Properties File


WSS4J needs to be feed with some crypto properties. These properties are grouped in three sections as shown below

#Crypto properties

#General properties:

#WSS4J specific provider used to create Crypto instances. Defaults to "org.apache.ws.security.components.crypto.Merlin".
#org.apache.ws.security.crypto.provider=

#The provider used to load keystores. Defaults to installed provider.
#org.apache.ws.security.crypto.merlin.keystore.provider=

#The provider used to load certificates. Defaults to keystore provider.
#org.apache.ws.security.crypto.merlin.cert.provider=

#The location of an (X509) CRL file to use.
#org.apache.ws.security.crypto.merlin.x509crl.file=

#Keystore properties:

#The location of the keystore
org.apache.ws.security.crypto.merlin.keystore.file=

#The password used to load the keystore. Default value is "security".
org.apache.ws.security.crypto.merlin.keystore.password=

#Type of keystore. Defaults to: java.security.KeyStore.getDefaultType()), normally it is JKS
#org.apache.ws.security.crypto.merlin.keystore.type=

#The default keystore alias to use, if none is specified.
org.apache.ws.security.crypto.merlin.keystore.alias=

#The default password used to load the private key. Normally it is provided through a password-callback class
#org.apache.ws.security.crypto.merlin.keystore.private.password=

#TrustStore properties:

#Whether or not to load the CA certs in ${java.home}/lib/security/cacerts (default is false)
#org.apache.ws.security.crypto.merlin.load.cacerts=

#The location of the truststore
#org.apache.ws.security.crypto.merlin.truststore.file=

#The truststore password. Defaults to "changeit".
#org.apache.ws.security.crypto.merlin.truststore.password=

#The truststore type. Defaults to: java.security.KeyStore.getDefaultType().
#org.apache.ws.security.crypto.merlin.truststore.type=

Some of the properties have default values which is fine with us. For the general properties we are fine with the default values, for the keystore properties we need some of them. But in this demo we do not need any truststore.  You may be wondering, why? Well, in simple cases, like this demo, we have only one certificate to trust i.e. Test CA's root certificate which has already been imported in the <Client/Server>KeyStore.jks and which is going to be pointed as keystore file that is why we do not need a separate truststore. But in cases where client's certificate is signed by a different CA and server does not want to import the same in its own keystore (which is recommended) then we need a separate truststore where client's signer's root CA need to be imported, otherwise server would not trust the client certificate. The same is also applicable when Client needs to trust a Server certificate which is signed by a different CA other than the Client's CA. 

So in our case we only need the properties marked in RED color. With those properties let us create to crypto files one for server and one for client.

server-crypto.properties
org.apache.ws.security.crypto.merlin.keystore.file=ServerKeyStore.jks
org.apache.ws.security.crypto.merlin.keystore.password=server-pass
org.apache.ws.security.crypto.merlin.keystore.alias=server

Copy this properties file to c:\>WSST\HelloWorldService\config\server-crypto.properties

client-crypto.properties
org.apache.ws.security.crypto.merlin.keystore.file=ClientKeyStore.jks
org.apache.ws.security.crypto.merlin.keystore.password=client-pass
org.apache.ws.security.crypto.merlin.keystore.alias=client

Copy this properties file to c:\>WSST\HelloWorldClient\config\client-crypto.properties

Write Password Callback Handler


Next we need to write password callback handlers for both client and server. So why do you need a password callback handler? You probably have noticed while creating the keystores, we have supplied two passwords, one for  the keystore itself and the other to preserve the private key. The keystore password is specified in the crypto properties file as shown in the previous section. But we have not supplied yet the private key password. In crypto properties file there is a property to specify this i.e.

#The default password used to load the private key. Normally it is provided through a password-callback class
#org.apache.ws.security.crypto.merlin.keystore.private.password=

But traditional practice is to provide through a password callback handler to make it more secure. Hmm it is private key password!! Being a Java class, the password callback handler can fetch the password from a Database, LDAP or from more secured places. In this case we would just echo the private key password from the password callback handler class. Below are our password callback handlers.

Server Private Key Password Callback Handler:

package server;

import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;

public class ServerPasswordCallback implements CallbackHandler {

public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {

for (int i = 0; i < callbacks.length; i++) {

WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];

if (pc.getUsage() == WSPasswordCallback.SIGNATURE
|| pc.getUsage() == WSPasswordCallback.DECRYPT)

if (pc.getIdentifier().equals("server"))
pc.setPassword("key-pass");
}

}
}


Client Private Key Password Callback Handler:

package client;

import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;

public class ClientPasswordCallback implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, 
        UnsupportedCallbackException {

    for (int i = 0; i < callbacks.length; i++) {
   
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
        
if (pc.getUsage() == WSPasswordCallback.SIGNATURE
|| pc.getUsage() == WSPasswordCallback.DECRYPT)

if (pc.getIdentifier().equals("client"))
pc.setPassword("key-pass");
    }
    }
}



Write WSS4J Interceptors


The final step is to write the inbound and outbound WSS4J interceptors for both client and server/service side. In total we have to write four interceptors. The interceptor beans configure the rest of the security characteristics like, whether the SOAP message will contain timestamp, username token, signature, encryption etc or not. What parts need to be signed or encrypted, what algorithm to use for different purposes, what type of BinarySecurityToken and reference would be used, pointer to Password Callback Handler class etc. All these settings are configured as Key/Value pairs in a map. The whole set is listed as WSHandler Tags in WSS4J configuration page. Please note many of the keys have default values.

Here is the client outbound/inbound WSS4J interceptors. Pay attention to outbound-security and inbound-security beans.

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<bean id="logInBound" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean id="logOutBound" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<jaxws:client id="helloClient" serviceClass="com.ddmwsst.helloworld.HelloWorld"
address="http://localhost:8080/helloworld/HelloWorld">
<jaxws:inInterceptors>
<ref bean="logInBound" />
<ref bean="inbound-security" />
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<ref bean="logOutBound" />
<ref bean="outbound-security" />
</jaxws:outInterceptors>
</jaxws:client>
<!-- WSS4JOutInterceptor for signing and encrypting outbound SOAP --> 
    <bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor" id="outbound-security">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt"/>  
                <entry key="user" value="client"/>              
                <entry key="signaturePropFile" value="client-crypto.properties"/>
                <entry key="encryptionPropFile" value="client-crypto.properties"/>
                <entry key="signatureKeyIdentifier" value="DirectReference"/>
                <entry key="encryptionUser" value="server"/>
                <entry key="passwordCallbackClass" value="client.ClientPasswordCallback"/>
                <entry key="signatureParts" value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
                <entry key="encryptionParts" value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
                <entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
            </map>
        </constructor-arg>
    </bean>
    
    <!--  WSS4JInInterceptor for decrypting and validating the signature of inbound SOAP -->
    <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor" id="inbound-security">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt"/>
                <entry key="signaturePropFile" value="client-crypto.properties"/>
                <entry key="decryptionPropFile" value="client-crypto.properties"/>
                <entry key="passwordCallbackClass" value="client.ClientPasswordCallback"/>
            </map>
        </constructor-arg>
    </bean>
</beans>

Explanation:

If you look at the outbound-security out-interceptor, it takes map as a constructor argument. And the map has a bunch of key value pairs. Most of them are self explanatory. 
The action key has a value of  "Timestamp Signature Encrypt" . This is how WSS4J is advised to put a timestamp in the SOAP message, and there is signing and encryption. 
The  signatureKeyIdentifier  says how the security token used in signing (here the client's public key) should be referenced. In this case it is a direct reference. If this is not specified, WSS4J uses a default as per X.509 certificate profile specification, which is IssuerSerial. Where it will just specify the issuer name of the certificate and the serial number and not the actual certificate. Since we have not imported client's public key in server's key store so we do need to send the whole certificate in the SOAP message itself. 
The key encryptionUser says that it would encrypt the message using a public key whose alias is server. Remember, we did import server's public key in client's key store at the beginning.
signatureParts and encryptionParts points to which parts need signature and encryption respectively. Please note that parts are specified as ';' separated and each part is defined as {Element|Content}{Fully qualified namespace} Part-Name.

Inbound is really simple, it just says that in the SOAP there should be a timestamp, signature and encryption. In-bound is not expecting that Part X must be signed or Part Y must be encrypted. Parts can be anything just that there should a signature element and an ecrypted element at the least. You may be wondering how inbound encrypted SOAP envelop getting parsed. But remember when we learnt about a security enabled SOAP envelop we told that instructions to process the message are embedded in the message itself.

Here is the server/service side inbound/outbound interceptors. Again pay attention to the inbound-security and outbound-security interceptors.

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<bean id="logInBound" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean id="logOutBound" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />

<jaxws:endpoint id="helloWorld"
implementor="server.HelloWorldImpl" address="/HelloWorld">
<jaxws:inInterceptors>
<ref bean="logInBound" />
<ref bean="inbound-security" />
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<ref bean="logOutBound" />
<ref bean="outbound-security" />
</jaxws:outInterceptors>
</jaxws:endpoint>

<!--  WSS4JInInterceptor for decrypting and validating the signature of inbound SOAP -->
    <bean id="inbound-security" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt"/>
                <entry key="signaturePropFile" value="server-crypto.properties"/>
                <entry key="decryptionPropFile" value="server-crypto.properties"/>
                <entry key="passwordCallbackClass" value="server.ServerPasswordCallback"/>
            </map>
        </constructor-arg>
    </bean>
    
    <!-- WSS4JOutInterceptor for signing and encrypting outbound SOAP -->     
    <bean id="outbound-security" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt"/>  
                <entry key="user" value="server"/>              
                <entry key="signaturePropFile" value="server-crypto.properties"/>
                <entry key="encryptionPropFile" value="server-crypto.properties"/>
                <entry key="encryptionUser" value="useReqSigCert"/>
                <entry key="passwordCallbackClass" value="server.ServerPasswordCallback"/>
                <entry key="signatureParts" value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
                <entry key="encryptionParts" value="{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
                <entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
            </map>
        </constructor-arg>
    </bean>   
    
</beans>

Explanation:

The inbound-security WSS4J interceptor is similar to client's inbound-security interceptor. Thanks to secured SOAP messages' self-instructed characteristic. The SOAP message can itself tell how it needs to be parsed/processed.
And most of the keys for outbound-security are also similar to client's outbound-security. But the following key-value looks odd right?

<entry key="encryptionUser" value="useReqSigCert"/>

It says useReqSigCert. You would have expected it to be client,  right?  After all, when server sends an encrypted message it should encrypt the message using clients public key. This is a special value in WSS4J it means use Request Signing Certificate. It allows any client having server's public key to send a message to the service/server. Server need not have client's public key in its keystores but would expect client's public key in the SOAP message itself. This is why we had used <entry key="signatureKeyIdentifier" value="DirectReference"/> in client's outbound-security interceptor to embed the actual certificate. If we had omitted this WSS4J by default would have used IssuerSerial as signatureKeyIdentifier and in that case only Issuer name and serial number would come in the BinarySecurityToken and not the client's public key. And server would not find it and fail to verify the client's signature breaking the communication path.

One more point to note here is that server outbound-security does not specify any key-identifier, neither for signature nor for encryption. So WSS4J would use the default key-identifier mechanism which is IssuerSerial. So for the signature server's outbound message will contain server certificate's issuer name and  the serial number and nothing else. As client has the server certificate in its key-store it is not going to be a problem. And for the encryption also server is going to specify client's certificate's issuer name and serial number only. Again it will not be an issue for client as client knows its own issuer and serial. Theoretically you can include server's and client's public key in the SOAP using e.g. DirectReference but it is unnecessary here.

Deploy Web Service



This step is simple and applicable only for the service side for this demo purpose. In the provided build.xml in the HelloWorldService project there is a target createWar. Please run that target and a war file should be generated. If you closely look at the ant target it makes sure that all the files in config directories are copied to WEB-INF/classes directory to make them available in the application classpath. Deploy the war n your Tomcat and makes sure it starts up without any error.

Test The Service


Once the service side or the server is ready let us send a secure request to the server. To do that we just need to run the client making sure crypto properties file and keystores are in classpath. I have provided a build.xml in the client project to a test client. Execute the runMain ant target and it should send a security enabled SOAP message to server. Server also should respond with a SOAP message as per security configuration. A sample request and response message are attached herewith for your inspection.

I have also attached the demo service and client projects at the attachment section.

Further Reading



Web Service Security For Java
Understanding Password Callback Handlers



ċ
HelloWorldClient.zip
(13k)
Debdayal Mandal,
Jan 24, 2012, 10:11 PM
ċ
HelloWorldService.zip
(13k)
Debdayal Mandal,
Jan 30, 2012, 8:12 PM
Comments