Tuesday, February 4, 2014

Web Service Security Using Axis1.4 and WSS4j1.5 for Message signing and Message encryption


Basic Signing and Encryption Scenario:

Overview

The scenario described here is a client-server application, where an asymmetric binding policy is set up to encrypt and sign the SOAP body of messages that pass back and forth between the client and the server.
Example scenario
Below shows an overview of the basic signing and encryption scenario, which is specified by associating an asymmetric binding policy with an endpoint in the WSDL contract.

Figure 1.1. Basic Signing and Encryption Scenario
WS-Security Message Signing and Encryption








Scenario steps
When the client in Figure 1.1 invokes a synchronous operation on the recipient's endpoint, the request and reply message are processed as follows:
  1. As the outgoing request message passes through the WS-SecurityPolicy handler, the handler processes the message in accordance with the policies specified in the client’s asymmetric binding policy. In this example, the handler performs the following processing:
    1. Encrypt the SOAP body of the message using Bob’s public key.
    2. Sign the encrypted SOAP body using Alice’s private key.
  2. As the incoming request message passes through the server's WS-SecurityPolicy handler, the handler processes the message in accordance with the policies specified in the server’s asymmetric binding policy. In this example, the handler performs the following processing:
    1. Verify the signature using Alice’s public key.
    2. Decrypt the SOAP body using Bob’s private key.
  3. As the outgoing reply message passes back through the server's WS-SecurityPolicy handler, the handler performs the following processing:
    1. Encrypt the SOAP body of the message using Alice’s public key.
    2. Sign the encrypted SOAP body using Bob’s private key.
  4. As the incoming reply message passes back through the client's WS-SecurityPolicy handler, the handler performs the following processing:
    1. Verify the signature using Bob’s public key.
    2. Decrypt the SOAP body using Alice’s private key.


Using Axis1.4 and WSS4j for message signature and Encryption

Server Side:

Required Third Party Jars: xalan-2.7.1.jar, xmlsec-1.4.5.jar, wss4j-1.5.12.jar and Axis1.4 jar files
Put all these files in your web application’s lib directory

Step1:
We need to create a public private key pair for message signing and encryption, We can use Java keytool for generating key store and keys using below commands
Below command  will create a file called server.keystore which contains a private and public key pair for encryption purpose. Make sure your java path is set and java home also set.
Go to command prompt, go to c:\ and some folder where you want to create your keystore like test
type below command with your own argument.
       a)       C:\test>  keytool -genkey -dname "CN=Server,OU=Encryption, O=Mycompany, L=Newyork, S=CT, C=US" -alias serverkey -keypass Welcome1 -validity 9999 -keyalg RSA -sigalg SHA1withRSA -keystore server.keystore -storepass Welcome1

explaination :where CN->Common Name,OU->Organisational Unit,0->Organisation,L->location,S->State,C->Country Code
keyalg-Key Alogrthim
sigalg-Signature Algorthim
keystore-keystorename
storepass-keystores password
keypass-Key password
validity-key Validity in days
-dname-distinguied name
b) This will create a file called client.keystore that contains a key pair for message signing. use your own arguments

keytool -genkey -dname "CN=Client, OU=Signing, O=Mycompany, L=NewYork, S=CT, C=US" -alias clientkey -keypass Welcome1 -validity 9999 -keyalg RSA -sigalg SHA1withRSA -keystore client.keystore -storepass Welcome1

c) .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 Welcome1 -file servercert.cer
keytool -import -alias serverkey -keystore client.keystore -storepass Welcome1 -file servercert.cer
d) 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 Welcome1 -file clientcert.cer

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


Step2: Update  server-config.wsdd of existing webservice located under WEB-INF directory
Add the highlighted code in the server-config.wsdd for your existing service here my existing service is UserSoap
see below I added the code in yelow to server-config.wsdd
<ns1:service name="UserSoap" provider="java:RPC" style="wrapped" use="literal">
  <ns3:operation name="changePassword" qname="ns1:changePassword" returnQName="ns1:changePasswordReturn" returnType="ns2:UserBean" soapAction="" xmlns:ns1="http://user.services.soap.lcc.mycompany.com" xmlns:ns2="http://beans.user.services.soap.lcc.mycompany.com" xmlns:ns3="http://xml.apache.org/axis/wsdd/">
   <ns3:parameter qname="ns1:newPassword" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
   <ns3:parameter qname="ns1:userId" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
   <ns3:parameter qname="ns1:oldPassword" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
   <ns3:parameter qname="ns1:retypePassword" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
   <ns3:parameter qname="ns1:profile_chgpwd" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
   <ns3:parameter qname="ns1:last_pwdchg" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
   <ns3:parameter qname="ns1:submission" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
  </ns3:operation>
  <ns1:requestFlow>
            <ns1:handler type="java:org.apache.ws.axis.security.WSDoAllReceiver">
            <ns1:parameter name="passwordCallbackClass" value="com.mycompany.lcc.soap.services.user.ChangePasswordCallBackHandler"/>
                <ns1:parameter name="action" value="Signature Encrypt"/>
                <ns1:parameter name="signaturePropFile" value="crypto.properties" />
                <parameter name="decryptionPropFile" value="crypto.properties" />
                <parameter name="encryptionPropFile" value="crypto.properties" />
            </ns1:handler>
   </ns1:requestFlow>
  <ns1:parameter name="allowedMethods" value="changePassword"/>
  <ns1:parameter name="typeMappingVersion" value="1.2"/>
  <ns1:parameter name="wsdlPortType" value="UserSoap"/>
  <ns1:parameter name="className" value="com.mycompany.lcc.soap.services.user.UserSoap"/>
  <ns1:parameter name="wsdlServicePort" value="UserSoap"/>
  <ns1:parameter name="schemaQualified" value="http://beans.user.services.soap.lcc.mycompany.com,http://user.services.soap.lcc.mycompany.com"/>
  <ns1:parameter name="wsdlTargetNamespace" value="http://user.services.soap.lcc.mycompany.com"/>
  <ns1:parameter name="wsdlServiceElement" value="UserSoapService"/>
  <ns1:typeMapping deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory" encodingStyle="" qname="ns3:UserBean" serializer="org.apache.axis.encoding.ser.BeanSerializerFactory" type="java:com.mycompany.lcc.soap.services.user.beans.UserBean" xmlns:ns3="http://beans.user.services.soap.lcc.mycompany.com"/>
 </ns1:service>
step3: Create a callback handler on server

package com.mycompany.lcc.soap.services.user;

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 ChangePasswordCallBackHandler implements  CallbackHandler{

                @Override
                public void handle(Callback[] callbacks) throws IOException,
                                                UnsupportedCallbackException {
                                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("Welcome1");
                }
                else if ("clientkey".equals(id)) {
                    pwcb.setPassword("Welcome1");
                }
            }
         }
    }         
                }
Put above class  inside classes or in classpath of weaplication
               
Step4:  Copy server.keystore file created in step 1a to WEB-INF/Classes directory
Step5: Create a property file named crypto.properties inside classes directory
Copy below code to crypto.properties
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=Welcome1
org.apache.ws.security.crypto.merlin.keystore.alias=serverkey
org.apache.ws.security.crypto.merlin.file=server.keystore

Step6 : Restart the tomcat Server,web service security with message encryption and signing is applied.

Client Side: Stand Alone Client:

Jars required: Required Third Party Jars: xalan-2.7.1.jar, xmlsec-1.4.5.jar, wss4j-1.5.12.jar and Axis1.4 jar files

Assumption: Webservice client Code is already generated using wsdl file
Step1: Copy the client.keystore file from Server Side Step 1b  to the root path of your client project.
Step2:Create a crypto.properties file and copy below code to it and put 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=Welcome1
org.apache.ws.security.crypto.merlin.keystore.alias=clientkey
org.apache.ws.security.crypto.merlin.file=client.keystore

Step3: Create a wsdd file called client_deploy.wsdd and copy it to the root class path
Contents of the wsdd file given below
<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="encryptionPropFile" value="crypto.properties" />
                <parameter name="passwordCallbackClass" value="com.ChangePwdClientCallBackHandler" />
               
            </handler>
           
    </globalConfiguration>
 </deployment>

Step4: Add the below Handler class to your client code
package com;

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 ChangePwdClientCallBackHandler implements CallbackHandler {

                @Override
                public void handle(Callback[] callbacks) throws IOException,
                                                UnsupportedCallbackException {
                               
                                System.out.println("Inside Handler");
                                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("Welcome1");
                   
                }
            }
        }
                               
                }
}

Step 5: Add the path to configuration(wsdd file) in your existing  web service calling code


package com;

import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;

import org.apache.axis.EngineConfiguration;
import org.apache.axis.configuration.FileProvider;

import com.mycompany.lcc.soap.services.user.UserSoap;
import com.mycompany.lcc.soap.services.user.UserSoapServiceLocator;
import com.mycompany.lcc.soap.services.user.beans.UserBean;

public class CpClient {
               
public static void main(String[] args) throws ServiceException ,RemoteException, MalformedURLException, FileNotFoundException{
                //System.setProperty("axis.ClientConfigFile", "client_deploy.wsdd");
               
     //Give relative path to wsdd file
                EngineConfiguration config = new FileProvider("./client_deploy.wsdd");
                UserSoapServiceLocator sl= new UserSoapServiceLocator(config);
                                UserSoap us = sl.getUserSoap(new java.net.URL("http://localhost:8080/services/UserSoap"));

//calling webservice method                             
UserBean u = us.changePassword("Test12345", "43243", "Test1234", "Test12345");
                System.out.println("isSuccess:"+u.getServiceSuccess());
                System.out.println("Error mesg: "+u.getErrorMsg());
                System.out.println("Success msg: "+u.getSuccessMsg());
}
}





Web Service Client User Guide for calling webservice inside  Web Application 
Running  client using  Axis1.4
A jar file containing all the java classes needed to call Axis1.4 web service  will be provided to end user
For Example- suppose this file name is ChangePasswordClient.jar

Steps to use in standalone/web project:
1-       Configure build path of project  and set ChangePasswordClient.jar  in class path as External  Jar in eclipse
2    Jar files required for writing simple Axis1.4 Client  
       Set below jar files in class path as external jars in eclipse
axis.jar
commons-discovery-0.2.jar
commons-logging.jar
jaxrpc.jar
saaj.jar
wsdl4j.jar
    Required Jars  for security : xalan-2.7.1.jar, xmlsec-1.4.5.jar, wss4j-1.5.12.jar 
3-       Create Java class and  call the webservice
Ex-


package com;

import java.net.MalformedURLException;
import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;

import com.mycompany.lcc.soap.services.user.UserSoap;
import com.mycompany.lcc.soap.services.user.UserSoapServiceLocator;
import com.mycompany.lcc.soap.services.user.beans.UserBean;

public class CPClient {
               
public static void main(String[] args)throws ServiceException ,RemoteException, MalformedURLException {
                               

EngineConfiguration config = new FileProvider("./client_deploy.wsdd");
                UserSoapServiceLocator sl= new UserSoapServiceLocator(config);
                System.out.println("test1");
                UserSoap us = sl.getUserSoap(new java.net.URL("http://localhost:8080/services/UserSoap"));
                                System.out.println("test2");
                //calling webservice method
                UserBean u = us.changePassword("Test12345", "14936079", "Test1234", "Test12345", null, null,null);
                System.out.println("isSuccess:"+u.getServiceSuccess());
                System.out.println("Error mesg: "+u.getErrorMsg());
                System.out.println("Success msg: "+u.getSuccessMsg());
                }

}

above code can be called from inside a servlet or POJO