Secure Apache XML-RPC

About

This document describes how to setup Apache XML-RPC to work using SSL (https) with server authentication.

 

Set up the environment

 

Generate server key using keytool

Type the following command:

keytool -genkey -keystore keyStoreServer -keyalg RSA -alias myKey -storepass blabla1 -keypass blabla2 -validity 180

Feel free to use your own keystore, storepass, keypass and validity parameters. In my case a file called keyStoreServer is created containing public and private key.

 

Create trust store for the client

keytool -export -keystore keyStoreServer -alias myKey -storepass blabla1 -file server.cer

 

In my case, a file called server.cer is created containing public key.

keytool -import -keystore trustStoreClient -alias myKey -file server.cer -storepass blabla1

 

In my case, a file called trustStoreClient is created.

 

Write your xml-rpc server

Apache XML-RPC comes with a SecureWebServer class. The best way to write a xml-rpc server is to extend SecureWebServer and override the createServerSocket(int port, int backlog, java.net.InetAddress add)method. See Listing 1:

 

Listing 1: (download file)

import org.apache.xmlrpc.secure.*;

import java.net.*;
import java.io.*;
import java.security.*;

import javax.net.ServerSocketFactory;
import javax.net.ssl.*;


public class MySecureWebServer extends SecureWebServer {
    private static final String SECURE_ALGORITHM = "SunX509";
    private static final String SECURE_PROTOCOL = "TLS";
    private static final String KEYSTORE_TYPE = "JKS";
	   private static final boolean requireClientAuth = false;
	
    private int serverPort;
    private String keyStoreFilename;
    private String keyStorePassword;
    private String keyPassword;
	
	   public MySecureWebServer(int serverPort, String keyStoreFilename, String keyStorePassword, 
									String keyPassword) {
		   super(serverPort);
		   this.serverPort = serverPort;
		   this.keyStoreFilename = keyStoreFilename;
		   this.keyStorePassword = keyStorePassword;
		   this.keyPassword = keyPassword;
	   }
	
	   protected java.net.ServerSocket createServerSocket(int port, int backlog, java.net.InetAddress add) throws java.lang.Exception {
		   return getServerSocket(port, keyStoreFilename, keyStorePassword, keyPassword, requireClientAuth);
	   }

	   ServerSocket getServerSocket(int serverPort, String keyStoreFilename, String keyStorePassword,
									String keyPassword, boolean requireClientAuth) throws Exception {
		  // Make sure that JSSE is available
		  Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
		  // A keystore is where keys and certificates are kept
		  // Both the keystore and individual private keys should be password protected
		  KeyStore keystore = KeyStore.getInstance(KEYSTORE_TYPE);
		  keystore.load(new FileInputStream(keyStoreFilename), keyStorePassword.toCharArray());
		  // A KeyManagerFactory is used to create key managers
		  KeyManagerFactory kmf = KeyManagerFactory.getInstance(SECURE_ALGORITHM);
		  
		  // Initialize the KeyManagerFactory to work with our keystore
		  kmf.init(keystore, keyPassword.toCharArray());
		  // An SSLContext is an environment for implementing JSSE
		  // It is used to create a ServerSocketFactory
		  SSLContext sslc = SSLContext.getInstance(SECURE_PROTOCOL);
		  // Initialize the SSLContext to work with our key managers
		  sslc.init(kmf.getKeyManagers(), null, null);
		  // Create a ServerSocketFactory from the SSLContext
		  ServerSocketFactory ssf = sslc.getServerSocketFactory();
		  // Socket to me
		  SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(serverPort);
		  // Authenticate the client?
		  serverSocket.setNeedClientAuth(requireClientAuth);
		  // Return a ServerSocket on the desired port
		  return serverSocket;
	  }
	
	
}

 

Write a class that utilizes the secure server:

Listing 2: (download file)

import java.util.Hashtable;


public class SecureXmlRpcServerDemo {
	  private static final int PORT_NUM = 443;
	  private static final String KEY_STORE = "KeyStoreServer";
	  private static final String KEY_STORE_PASS = "blabla1";
	  private static final String KEY_PASS = "blabla2";
	
	  public SecureXmlRpcServerDemo() {
		
	  }

   public Hashtable sumAndDifference (int x, int y) {
       System.out.println("method sumAndDifference() invoked!");
       Hashtable result = new Hashtable();
       result.put("sum", new Integer(x + y));
       result.put("difference", new Integer(x - y));
       return result;
   }

	  public static void main(String[] args) {
       try {
	          MySecureWebServer server = new MySecureWebServer(PORT_NUM, KEY_STORE, KEY_STORE_PASS, KEY_PASS);
			  server.addHandler("sample", new SecureXmlRpcServerDemo());
			  server.setParanoid(false);
			  server.start();
			  System.out.println("Server started!");
			  System.out.println("Listening on port: "+PORT_NUM);
		  }
		  catch (Exception e) {
	          e.printStackTrace(System.err);
		  }
		
	  }
 }  

 

Notes about Listing 2: You can use server.setParanoid(true) to allow only certain IPs to connect to server. You add the IPs you trust with server.acceptClient() method

 

Write the client code:

Listing 3: (download file)

import java.io.*;
import java.net.MalformedURLException;
import java.util.Hashtable;
import java.util.Vector;

import javax.net.ssl.*;

import org.apache.xmlrpc.*;
import org.apache.xmlrpc.secure.*;


public class SecureXmlRpcClientDemo {
	   private final static int PORT_NUM = 443;
	   private final static String serverURL = "https://localhost:"+String.valueOf(PORT_NUM);
	   private final static String trustStoreFilename = "trustStoreClient";
	
	   public static void main(String[] args) {
        try {
            System.setProperty("javax.net.ssl.trustStore", trustStoreFilename);
            setHostNameVerifier();
            
            SecureXmlRpcClient client = new SecureXmlRpcClient(serverURL);

            // Fill in the appropriate method call that's available on your server
            String method = "sample.sumAndDifference";
            // Build our parameter list.
            Vector params = new Vector();
            params.addElement(new Integer(10));
            params.addElement(new Integer(7));

            System.out.println("Invoking remote method:");
            // Call the server, and get our result.
            Hashtable result = (Hashtable) client.execute(method, params);
     
            int sum = ((Integer) result.get("sum")).intValue();
            int difference = ((Integer) result.get("difference")).intValue();
            // Print out our result.
            System.out.println("Sum: " + Integer.toString(sum) + ", Difference: " + Integer.toString(difference));

        } 
        catch (XmlRpcException e) {
            e.printStackTrace();
        } 
        catch (MalformedURLException e) {
        	   e.printStackTrace();
        } 
        catch (IOException e) {
        	   e.printStackTrace();
        } 
        catch (Exception e) {
        	   e.printStackTrace();
        }
	
	}
	
	private static void setHostNameVerifier() {
		HostnameVerifier hv = new HostnameVerifier() {
		    public boolean verify(String urlHostName, SSLSession session) {
		        System.out.println("Warning: URL Host: "+urlHostName+" vs. "+session.getPeerHost());
		        return true;
		    }
		};
		HttpsURLConnection.setDefaultHostnameVerifier(hv);
	}
} 

 

Notes about Listing 3:

Alternative to setting

System.setProperty("javax.net.ssl.trustStore", trustStoreFilename);

is to put your trust store file in one of the following files under JAVA_HOME directory where it will be automaticaly found:

 

Do not forget to set the host name verifier! If you do not set it, you will probably get the following exception:

java.io.IOException: HTTPS hostname wrong: should be <localhost>

 

Here is the alternative to client code presented in Listing 3.

 

 

Author: Andrej Furlan

Last change: August 11, 2005