Advanced java code dealing with real world problems.

Monday, November 30, 2009

Use wss4j with Axis1.4 for message encryption and signing

Use wss4j with Axis1.4 for message encryption and signing.

Axis 1.4 and wss4j have been out for quite a while, but the detailed documentations about using them together is hard to find. Detailed documentations are abundant for using Rampant with Axis 2. But if you are like me stuck to the Axis 1 and want to encrypt and sign your messages without modifying your existing code, wss4j is still the best option out there.

The Apache wss4j web site provides some wonderful documentations about Axis deployment tutorial and samples. The hard part is how to put everything together. What I am about to offer here is the detailed steps about putting everything together so that you can implement the message encryption and signing with your existing web services using wss4j with Axis 1.4.

I will take a different approach here, let's tackle the key stores first.

To be able to handle encrypted messages, you'll need a pair of keys, a public key that is used to encrypt messages which the client will use, and a private key that is used to decrypt messages which you keep it safe in your server.  To generate self-signed key stores using java keytool, issue following command in a dos window or a shell prompt:

keytool -genkey -dname "CN=Server, OU=Encryption, O=JacksBlog, L=Raleigh, S=NC, C=US" -alias serverkey -keypass serverpass -validity 9999 -keyalg RSA -sigalg SHA1withRSA -keystore server.keystore -storepass nosecret

This will create a file called server.keystore which contains a private and public key pair for encryption purpose.

Next we will create a key pair for message signing:

keytool -genkey -dname "CN=Client, OU=Signing, O=JacksBlog, L=Raleigh, S=NC, C=US" -alias clientkey -keypass clientpass -validity 9999 -keyalg RSA -sigalg SHA1withRSA -keystore client.keystore -storepass nosecret

This will create a file called client.keystore that contains a key pair for message signing.

In order for the client to trust the server, we need to export the public key from server.keystore and import it to client.keystore:

keytool -export -alias serverkey -keystore server.keystore -storepass nosecret -file servercert.cer

keytool -import -alias serverkey -keystore client.keystore -storepass nosecret -file servercert.cer

In order for the server to trust the client, we need to export the public key from client.keystore and import it to server.keystore:

keytool -export -alias clientkey -keystore client.keystore -storepass nosecret -file clientcert.cer

keytool -import -alias clientkey -keystore server.keystore -storepass nosecret -file clientcert.cer

Now we are ready for Axis configurations, please refer to wss4j web site for how to install wss4j on Axis 1.4. I'll highlight a couple of key points here:

1) Download the binary distribution from Apache wss4j web site, and unzip it into a folder. Make sure to read through the README.txt file, and download all the required jar files listed in the README.txt file.

2) Copy all the required jar files along with wss4j jar file to your Axis's WEB-INF/lib directory.

Now the detailed steps:

3) Create a password callback class in your Axis project and compile it to your class path:

public class PasswordCallBackHandler implements CallbackHandler {
    public void handle(Callback[] callbacks) throws IOException {
        for (int i = 0; i < callbacks.length; i++) {
            WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
            String id = pwcb.getIdentifier();
            int usage = pwcb.getUsage();
            if (usage == WSPasswordCallback.DECRYPT || usage == WSPasswordCallback.SIGNATURE) {
                // used to retrieve password for private key
                if ("serverkey".equals(id)) {
                    pwcb.setPassword("serverpass");
                }
                else if ("clientkey".equals(id)) {
                    pwcb.setPassword("clientpass");
                }
            }
         }
    }
}

4) Create a folder called Keys under WEB-INF/classes and copy the server.keystore file to the folder.

5) Create a crypto.properties file and copy it to WEB-INF/classes folder:

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=nosecret
org.apache.ws.security.crypto.merlin.keystore.alias=serverkey
org.apache.ws.security.crypto.merlin.file=Keys/server.keystore

6) Pick up an existing web service from your Axis project (or simply create a new one) that you want to have messages encrypted and signed, and add the required entries to your server-config.wsdd file. For example I picked up a service call MyService and added "requestFlow" entry into the wsdd:

    <service name="MyService" provider="java:MSG">
        <requestFlow>
            <handler type="java:org.apache.ws.axis.security.WSDoAllReceiver">
                <parameter name="action" value="Signature Encrypt"/>
                <parameter name="signaturePropFile" value="./WEB-INF/classes/crypto.properties" />
                 <parameter name="passwordCallbackClass" value="PasswordCallBackHandler"/>
            </handler>
          </requestFlow>
        <parameter name="className" value="blog.MyService" />
        <parameter name="allowedMethods" value="process" />
    </service>

7) Restart your server and the MyService should be ready to serve encrypted and signed service calls.

Now let's modify the client code to call the service.

1) Copy the client.keystore file to the root path of your client project.

2) Create a crypto.properties file and copy it to the root class path:

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=nosecret
org.apache.ws.security.crypto.merlin.keystore.alias=clientkey
org.apache.ws.security.crypto.merlin.file=client.keystore

3) Create a wsdd file called client_deploy.wsdd and copy it to the root class path (please change the "passwordCallbackClass" value accordingly):

<deployment xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
    <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender" />
    <globalConfiguration>
        <requestFlow>
            <handler type="java:org.apache.ws.axis.security.WSDoAllSender">
                <parameter name="user" value="clientkey" />

                <parameter name="encryptionUser" value="serverkey"/>
                <parameter name="action" value="Signature Encrypt" />
                <parameter name="signaturePropFile" value="crypto.properties" />
                <parameter name="passwordCallbackClass" value="blog.ServiceClient" />
            </handler>
        </requestFlow>
    </globalConfiguration>
 </deployment>
4) Modify the client code, add this line before the service call is initialized:

        System.setProperty("axis.ClientConfigFile", "client_deploy.wsdd");

5) Add the following method to your client code, make sure the client class implements CallbackHandler:

    public void handle(Callback[] callbacks) throws IOException {
        for (int i = 0; i < callbacks.length; i++) {
            WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
            String id = pwcb.getIdentifier();
            int usage = pwcb.getUsage();
            if (usage == WSPasswordCallback.DECRYPT || usage == WSPasswordCallback.SIGNATURE) {
                // used to retrieve password for private key
                if ("clientkey".equals(id)) {
                    pwcb.setPassword("clientpass");
                }
            }
        }
    }

That should be it. Compile and run your client class and keep your fingers crossed.

Followers

About Me

An IT professional with more than 20 years of experience in enterprise computing. An Audio enthusiast designed and built DIY audio gears and speakers.